diff --git a/app/code/Magento/Backend/Block/Widget/Form.php b/app/code/Magento/Backend/Block/Widget/Form.php index 59b5cc060cc05..38d5d90a22d15 100644 --- a/app/code/Magento/Backend/Block/Widget/Form.php +++ b/app/code/Magento/Backend/Block/Widget/Form.php @@ -59,7 +59,6 @@ protected function _construct() parent::_construct(); $this->setDestElementId('edit_form'); - $this->setShowGlobalIcon(false); } /** diff --git a/app/code/Magento/Backend/Block/Widget/Grid.php b/app/code/Magento/Backend/Block/Widget/Grid.php index 72ab5a265d808..66298d23389fb 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid.php +++ b/app/code/Magento/Backend/Block/Widget/Grid.php @@ -12,7 +12,7 @@ * @api * @deprecated 100.2.0 in favour of UI component implementation * @method string getRowClickCallback() getRowClickCallback() - * @method \Magento\Backend\Block\Widget\Grid setRowClickCallback() setRowClickCallback(string $value) + * @method \Magento\Backend\Block\Widget\Grid setRowClickCallback(string $value) * @SuppressWarnings(PHPMD.TooManyFields) * @since 100.0.2 */ @@ -150,7 +150,10 @@ public function __construct( } /** + * Internal constructor, that is called from real constructor + * * @return void + * * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _construct() @@ -709,6 +712,7 @@ public function getGridUrl() /** * Grid url getter + * * Version of getGridUrl() but with parameters * * @param array $params url parameters diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php index a08142c10be5e..2df0ff0b6cd7c 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php @@ -64,17 +64,6 @@ public function __construct( parent::__construct($context, $registry, $formFactory, $data); } - /** - * Construct block - * - * @return void - */ - protected function _construct() - { - parent::_construct(); - $this->setShowGlobalIcon(true); - } - /** * Prepares form * diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php index 0c547f81c85d6..596cd7cc5bdce 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php @@ -9,6 +9,9 @@ */ namespace Magento\Catalog\Block\Product\ProductList; +/** + * Crosssell block for product + */ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct { /** @@ -25,7 +28,7 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct */ protected function _prepareData() { - $product = $this->_coreRegistry->registry('product'); + $product = $this->getProduct(); /* @var $product \Magento\Catalog\Model\Product */ $this->_itemCollection = $product->getCrossSellProductCollection()->addAttributeToSelect( @@ -43,6 +46,7 @@ protected function _prepareData() /** * Before rendering html process + * * Prepare items collection * * @return \Magento\Catalog\Block\Product\ProductList\Crosssell diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php index 219922f9e46d5..6de70bb971367 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php @@ -77,11 +77,13 @@ public function __construct( } /** + * Prepare data + * * @return $this */ protected function _prepareData() { - $product = $this->_coreRegistry->registry('product'); + $product = $this->getProduct(); /* @var $product \Magento\Catalog\Model\Product */ $this->_itemCollection = $product->getRelatedProductCollection()->addAttributeToSelect( @@ -103,6 +105,8 @@ protected function _prepareData() } /** + * Before to html handler + * * @return $this */ protected function _beforeToHtml() @@ -112,6 +116,8 @@ protected function _beforeToHtml() } /** + * Get collection items + * * @return Collection */ public function getItems() diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index 0d64ecc9bff90..24822447ae915 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -91,11 +91,13 @@ public function __construct( } /** + * Prepare data + * * @return $this */ protected function _prepareData() { - $product = $this->_coreRegistry->registry('product'); + $product = $this->getProduct(); /* @var $product \Magento\Catalog\Model\Product */ $this->_itemCollection = $product->getUpSellProductCollection()->setPositionOrder()->addStoreFilter(); if ($this->moduleManager->isEnabled('Magento_Checkout')) { @@ -121,6 +123,8 @@ protected function _prepareData() } /** + * Before to html handler + * * @return $this */ protected function _beforeToHtml() @@ -130,6 +134,8 @@ protected function _beforeToHtml() } /** + * Get items collection + * * @return Collection */ public function getItemCollection() @@ -145,6 +151,8 @@ public function getItemCollection() } /** + * Get collection items + * * @return \Magento\Framework\DataObject[] */ public function getItems() @@ -156,6 +164,8 @@ public function getItems() } /** + * Get row count + * * @return float */ public function getRowCount() @@ -164,6 +174,8 @@ public function getRowCount() } /** + * Set column count + * * @param string $columns * @return $this */ @@ -176,6 +188,8 @@ public function setColumnCount($columns) } /** + * Get column count + * * @return int */ public function getColumnCount() @@ -184,6 +198,8 @@ public function getColumnCount() } /** + * Reset items iterator + * * @return void */ public function resetItemsIterator() @@ -193,6 +209,8 @@ public function resetItemsIterator() } /** + * Get iterable item + * * @return mixed */ public function getIterableItem() @@ -204,6 +222,7 @@ public function getIterableItem() /** * Set how many items we need to show in upsell block + * * Notice: this parameter will be also applied * * @param string $type @@ -219,6 +238,8 @@ public function setItemLimit($type, $limit) } /** + * Get item limit + * * @param string $type * @return array|int */ diff --git a/app/code/Magento/Catalog/Controller/Product/Compare.php b/app/code/Magento/Catalog/Controller/Product/Compare.php index 1ee146e5aaa70..084a82f87d645 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Controller\Product; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\View\Result\PageFactory; @@ -15,7 +16,7 @@ * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -abstract class Compare extends \Magento\Framework\App\Action\Action +abstract class Compare extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** * Customer id @@ -139,4 +140,15 @@ public function setCustomerId($customerId) $this->_customerId = $customerId; return $this; } + + /** + * @inheritdoc + */ + public function execute() + { + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('catalog/product_compare'); + + return $resultRedirect; + } } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index a077eced6d5d5..fd1b6add8391f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -213,4 +213,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index d12233206ce41..59228d57502f1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -30,5 +30,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml index a8b2df1fcfa67..f283a040ced41 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml @@ -59,6 +59,7 @@ + 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 d84f496e81915..7379600011bcf 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 @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Attribute\ScopeOverriddenValue; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Type as ProductType; use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute; use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory as EavAttributeFactory; use Magento\Catalog\Ui\DataProvider\CatalogEavValidationRules; @@ -419,7 +420,7 @@ public function modifyData(array $data) foreach ($attributes as $attribute) { if (null !== ($attributeValue = $this->setupAttributeData($attribute))) { - if ($attribute->getFrontendInput() === 'price' && is_scalar($attributeValue)) { + if ($this->isPriceAttribute($attribute, $attributeValue)) { $attributeValue = $this->formatPrice($attributeValue); } $data[$productId][self::DATA_SOURCE_DEFAULT][$attribute->getAttributeCode()] = $attributeValue; @@ -430,6 +431,32 @@ public function modifyData(array $data) return $data; } + /** + * Obtain if given attribute is a price + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @param string|integer $attributeValue + * @return bool + */ + private function isPriceAttribute(ProductAttributeInterface $attribute, $attributeValue) + { + return $attribute->getFrontendInput() === 'price' + && is_scalar($attributeValue) + && !$this->isBundleSpecialPrice($attribute); + } + + /** + * Obtain if current product is bundle and given attribute is special_price + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @return bool + */ + private function isBundleSpecialPrice(ProductAttributeInterface $attribute) + { + return $this->locator->getProduct()->getTypeId() === ProductType::TYPE_BUNDLE + && $attribute->getAttributeCode() === ProductAttributeInterface::CODE_SPECIAL_PRICE; + } + /** * Resolve data persistence * diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 9080dab0a5c13..ae97d07ef6256 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -132,6 +132,16 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ const COL_NAME = 'name'; + /** + * Column new_from_date. + */ + const COL_NEW_FROM_DATE = 'new_from_date'; + + /** + * Column new_to_date. + */ + const COL_NEW_TO_DATE = 'new_to_date'; + /** * Column product website. */ @@ -298,6 +308,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity 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_MULTISELECT_VALUES => "Value for multiselect attribute %s contains duplicated values", + ValidatorInterface::ERROR_NEW_TO_DATE => 'Make sure new_to_date is later than or the same as new_from_date', ]; //@codingStandardsIgnoreEnd @@ -319,8 +330,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity Product::COL_TYPE => 'product_type', Product::COL_PRODUCT_WEBSITES => 'product_websites', 'status' => 'product_online', - 'news_from_date' => 'new_from_date', - 'news_to_date' => 'new_to_date', + 'news_from_date' => self::COL_NEW_FROM_DATE, + 'news_to_date' => self::COL_NEW_TO_DATE, 'options_container' => 'display_product_options_in', 'minimal_price' => 'map_price', 'msrp' => 'msrp_price', @@ -2549,6 +2560,20 @@ public function validateRow(array $rowData, $rowNum) } } } + + if (!empty($rowData[self::COL_NEW_FROM_DATE]) && !empty($rowData[self::COL_NEW_TO_DATE]) + ) { + $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_FROM_DATE], false)); + $newToTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_TO_DATE], false)); + if ($newFromTimestamp > $newToTimestamp) { + $this->addRowError( + ValidatorInterface::ERROR_NEW_TO_DATE, + $rowNum, + $rowData[self::COL_NEW_TO_DATE] + ); + } + } + return !$this->getErrorAggregator()->isRowInvalid($rowNum); } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php index f41596ad185a6..cbdc5f5beaaf9 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php @@ -87,6 +87,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn const ERROR_DUPLICATE_MULTISELECT_VALUES = 'duplicatedMultiselectValues'; + const ERROR_NEW_TO_DATE = 'invalidNewToDateValue'; + /** * Value that means all entities (e.g. websites, groups etc.) */ diff --git a/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php new file mode 100644 index 0000000000000..4e8e3840693a5 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php @@ -0,0 +1,88 @@ +request = $request; + } + + /** + * Add 'save_rewrites_history' param to the product data + * + * @see \Magento\CatalogUrlRewrite\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper + * @param \Magento\Webapi\Controller\Rest\InputParamsResolver $subject + * @param array $result + * @return array + */ + public function afterResolve(\Magento\Webapi\Controller\Rest\InputParamsResolver $subject, array $result): array + { + $route = $subject->getRoute(); + $serviceMethodName = $route->getServiceMethod(); + $serviceClassName = $route->getServiceClass(); + $requestBodyParams = $this->request->getBodyParams(); + + if ($this->isProductSaveCalled($serviceClassName, $serviceMethodName) + && $this->isCustomAttributesExists($requestBodyParams)) { + foreach ($requestBodyParams['product']['custom_attributes'] as $attribute) { + if ($attribute['attribute_code'] === 'save_rewrites_history') { + foreach ($result as $resultItem) { + if ($resultItem instanceof \Magento\Catalog\Model\Product) { + $resultItem->setData('save_rewrites_history', (bool)$attribute['value']); + break 2; + } + } + break; + } + } + } + return $result; + } + + /** + * Check that product save method called + * + * @param string $serviceClassName + * @param string $serviceMethodName + * @return bool + */ + private function isProductSaveCalled(string $serviceClassName, string $serviceMethodName): bool + { + return $serviceClassName === ProductRepositoryInterface::class && $serviceMethodName === 'save'; + } + + /** + * Check is any custom options exists in product data + * + * @param array $requestBodyParams + * @return bool + */ + private function isCustomAttributesExists(array $requestBodyParams): bool + { + return !empty($requestBodyParams['product']['custom_attributes']); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php new file mode 100644 index 0000000000000..8e705b2c09f6b --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php @@ -0,0 +1,116 @@ +saveRewritesHistory = 'save_rewrites_history'; + $this->requestBodyParams = [ + 'product' => [ + 'sku' => 'test', + 'custom_attributes' => [ + ['attribute_code' => $this->saveRewritesHistory, 'value' => 1] + ] + ] + ]; + + $this->route = $this->createPartialMock(Route::class, ['getServiceMethod', 'getServiceClass']); + $this->request = $this->createPartialMock(RestRequest::class, ['getBodyParams']); + $this->request->expects($this->any())->method('getBodyParams')->willReturn($this->requestBodyParams); + $this->subject = $this->createPartialMock(InputParamsResolver::class, ['getRoute']); + $this->subject->expects($this->any())->method('getRoute')->willReturn($this->route); + $this->product = $this->createPartialMock(Product::class, ['setData']); + + $this->result = [false, $this->product, 'test']; + + $this->objectManager = new ObjectManager($this); + $this->plugin = $this->objectManager->getObject( + InputParamsResolverPlugin::class, + [ + 'request' => $this->request + ] + ); + } + + public function testAfterResolve() + { + $this->route->expects($this->once()) + ->method('getServiceClass') + ->willReturn(ProductRepositoryInterface::class); + $this->route->expects($this->once()) + ->method('getServiceMethod') + ->willReturn('save'); + $this->product->expects($this->once()) + ->method('setData') + ->with($this->saveRewritesHistory, true); + + $this->plugin->afterResolve($this->subject, $this->result); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index e373d8c8c1756..b4ceff96b50b7 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -16,6 +16,9 @@ "magento/module-ui": "*", "magento/module-url-rewrite": "*" }, + "suggest": { + "magento/module-webapi": "*" + }, "type": "magento2-module", "license": [ "OSL-3.0", diff --git a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml index ac8beb362f0fb..34b7487725d76 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml @@ -7,4 +7,7 @@ --> + + + diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml index 1876cf2edb786..da0a83f05ef60 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/renderer/actions/edit.phtml @@ -12,8 +12,6 @@ - - - - + + diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/Blocks.php b/app/code/Magento/CmsGraphQl/Model/Resolver/Blocks.php index 962127e16f716..e55db2a3fa42a 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/Blocks.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/Blocks.php @@ -55,6 +55,8 @@ public function resolve( } /** + * Get block identifiers + * * @param array $args * @return string[] * @throws GraphQlInputException @@ -69,6 +71,8 @@ private function getBlockIdentifiers(array $args): array } /** + * Get blocks data + * * @param array $blockIdentifiers * @return array * @throws GraphQlNoSuchEntityException @@ -76,12 +80,12 @@ private function getBlockIdentifiers(array $args): array private function getBlocksData(array $blockIdentifiers): array { $blocksData = []; - try { - foreach ($blockIdentifiers as $blockIdentifier) { + foreach ($blockIdentifiers as $blockIdentifier) { + try { $blocksData[$blockIdentifier] = $this->blockDataProvider->getData($blockIdentifier); + } catch (NoSuchEntityException $e) { + $blocksData[$blockIdentifier] = new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } - } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); } return $blocksData; } diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php index 5b7e632a73cb0..47a2439c4fad0 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php @@ -40,6 +40,8 @@ public function __construct( } /** + * Get block data + * * @param string $blockIdentifier * @return array * @throws NoSuchEntityException @@ -49,7 +51,9 @@ public function getData(string $blockIdentifier): array $block = $this->blockRepository->getById($blockIdentifier); if (false === $block->isActive()) { - throw new NoSuchEntityException(); + throw new NoSuchEntityException( + __('The CMS block with the "%1" ID doesn\'t exist.', $blockIdentifier) + ); } $renderedContent = $this->widgetFilter->filter($block->getContent()); diff --git a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml index b9fddc46f78ec..dd42b408ff75e 100644 --- a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml @@ -92,15 +92,15 @@ - 1 elasticsearch5 + 1 - 1 elasticsearch5 + 1 diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php index e490ee4018376..ba37cc8aacff9 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/History/Download.php @@ -1,20 +1,28 @@ subscriberFactory = $subscriberFactory; $this->extensionFactory = $extensionFactory; $this->subscriberResource = $subscriberResource; + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -151,7 +164,8 @@ public function afterDelete(CustomerRepository $subject, $result, CustomerInterf public function afterGetById(CustomerRepository $subject, CustomerInterface $customer) { $extensionAttributes = $customer->getExtensionAttributes(); - + $storeId = $this->storeManager->getStore()->getId(); + $customer->setStoreId($storeId); if ($extensionAttributes === null) { /** @var CustomerExtensionInterface $extensionAttributes */ $extensionAttributes = $this->extensionFactory->create(CustomerInterface::class); diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php index e809b7e37a432..3be28cacc93e0 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php @@ -10,6 +10,8 @@ use Magento\Customer\Api\Data\CustomerExtensionInterface; use Magento\Framework\Api\ExtensionAttributesFactory; use Magento\Newsletter\Model\ResourceModel\Subscriber; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; class CustomerPluginTest extends \PHPUnit\Framework\TestCase { @@ -53,6 +55,11 @@ class CustomerPluginTest extends \PHPUnit\Framework\TestCase */ private $customerMock; + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + protected function setUp() { $this->subscriberFactory = $this->getMockBuilder(\Magento\Newsletter\Model\SubscriberFactory::class) @@ -87,6 +94,8 @@ protected function setUp() ->setMethods(['getExtensionAttributes']) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->subscriberFactory->expects($this->any())->method('create')->willReturn($this->subscriber); $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -96,6 +105,7 @@ protected function setUp() 'subscriberFactory' => $this->subscriberFactory, 'extensionFactory' => $this->extensionFactoryMock, 'subscriberResource' => $this->subscriberResourceMock, + 'storeManager' => $this->storeManagerMock, ] ); } @@ -206,6 +216,7 @@ public function testAfterGetByIdCreatesExtensionAttributesIfItIsNotSet( ) { $subject = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); $subscriber = [$subscriberStatusKey => $subscriberStatusValue]; + $this->prepareStoreData(); $this->extensionFactoryMock->expects($this->any()) ->method('create') @@ -233,6 +244,7 @@ public function testAfterGetByIdSetsIsSubscribedFlagIfItIsNotSet() { $subject = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); $subscriber = ['subscriber_id' => 1, 'subscriber_status' => 1]; + $this->prepareStoreData(); $this->customerMock->expects($this->any()) ->method('getExtensionAttributes') @@ -267,4 +279,17 @@ public function afterGetByIdDataProvider() [null, null, false], ]; } + + /** + * Prepare store information + * + * @return void + */ + private function prepareStoreData() + { + $storeId = 1; + $storeMock = $this->createMock(Store::class); + $storeMock->expects($this->any())->method('getId')->willReturn($storeId); + $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); + } } diff --git a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js index fccc8510ffc70..2e8a4769be10b 100644 --- a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js +++ b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js @@ -6,9 +6,10 @@ define([ 'jquery', 'domReady', + 'consoleLogger', 'jquery/ui', 'mage/cookies' -], function ($, domReady) { +], function ($, domReady, consoleLogger) { 'use strict'; /** @@ -35,7 +36,9 @@ define([ * @returns {Array} */ $.fn.comments = function () { - var elements = []; + var elements = [], + contents, + elementContents; /** * @param {jQuery} element - Comment holder @@ -46,14 +49,35 @@ define([ // prevent cross origin iframe content reading if ($(element).prop('tagName') === 'IFRAME') { iframeHostName = $('').prop('href', $(element).prop('src')) - .prop('hostname'); + .prop('hostname'); if (window.location.hostname !== iframeHostName) { return []; } } - $(element).contents().each(function (index, el) { + /** + * Rewrite jQuery contents(). + * + * @param {jQuery} elem + */ + contents = function (elem) { + return $.map(elem, function (el) { + try { + return $.nodeName(el, 'iframe') ? + el.contentDocument || (el.contentWindow ? el.contentWindow.document : []) : + $.merge([], el.childNodes); + } catch (e) { + consoleLogger.error(e); + + return []; + } + }); + }; + + elementContents = contents($(element)); + + $.each(elementContents, function (index, el) { switch (el.nodeType) { case 1: // ELEMENT_NODE lookup(el); diff --git a/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml b/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml deleted file mode 100644 index 1961b1002df70..0000000000000 --- a/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml deleted file mode 100644 index 0ca252aa73545..0000000000000 --- a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - -
- -
-
diff --git a/app/code/Magento/Review/Model/ResourceModel/Rating.php b/app/code/Magento/Review/Model/ResourceModel/Rating.php index 3f54c17f6ff7c..78020ce3f8184 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Rating.php +++ b/app/code/Magento/Review/Model/ResourceModel/Rating.php @@ -178,6 +178,8 @@ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) } /** + * Process rating codes + * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ @@ -201,6 +203,8 @@ protected function processRatingCodes(\Magento\Framework\Model\AbstractModel $ob } /** + * Process rating stores + * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ @@ -224,6 +228,8 @@ protected function processRatingStores(\Magento\Framework\Model\AbstractModel $o } /** + * Delete rating data + * * @param int $ratingId * @param string $table * @param array $storeIds @@ -247,6 +253,8 @@ protected function deleteRatingData($ratingId, $table, array $storeIds) } /** + * Insert rating data + * * @param string $table * @param array $data * @return void @@ -269,6 +277,7 @@ protected function insertRatingData($table, array $data) /** * Perform actions after object delete + * * Prepare rating data for reaggregate all data for reviews * * @param \Magento\Framework\Model\AbstractModel $object @@ -425,9 +434,11 @@ public function getReviewSummary($object, $onlyForCurrentStore = true) $data = $connection->fetchAll($select, [':review_id' => $object->getReviewId()]); + $currentStore = $this->_storeManager->isSingleStoreMode() ? $this->_storeManager->getStore()->getId() : null; + if ($onlyForCurrentStore) { foreach ($data as $row) { - if ($row['store_id'] == $this->_storeManager->getStore()->getId()) { + if ($row['store_id'] !== $currentStore) { $object->addData($row); } } diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php index c00af3fc61407..e689d4ed460ac 100644 --- a/app/code/Magento/Review/Model/Review.php +++ b/app/code/Magento/Review/Model/Review.php @@ -5,6 +5,7 @@ */ namespace Magento\Review\Model; +use Magento\Framework\DataObject; use Magento\Catalog\Model\Product; use Magento\Framework\DataObject\IdentityInterface; use Magento\Review\Model\ResourceModel\Review\Product\Collection as ProductCollection; @@ -327,6 +328,9 @@ public function appendSummary($collection) $item->setRatingSummary($summary); } } + if (!$item->getRatingSummary()) { + $item->setRatingSummary(new DataObject()); + } } return $this; diff --git a/app/code/Magento/Sales/Block/Order/Items.php b/app/code/Magento/Sales/Block/Order/Items.php index 028544cd56219..d7255a24aead5 100644 --- a/app/code/Magento/Sales/Block/Order/Items.php +++ b/app/code/Magento/Sales/Block/Order/Items.php @@ -5,13 +5,13 @@ */ /** - * Sales order view items block - * * @author Magento Core Team */ namespace Magento\Sales\Block\Order; /** + * Sales order view items block. + * * @api * @since 100.0.2 */ @@ -71,7 +71,6 @@ protected function _prepareLayout() $this->itemCollection = $this->itemCollectionFactory->create(); $this->itemCollection->setOrderFilter($this->getOrder()); - $this->itemCollection->filterByParent(null); /** @var \Magento\Theme\Block\Html\Pager $pagerBlock */ $pagerBlock = $this->getChildBlock('sales_order_item_pager'); @@ -87,8 +86,9 @@ protected function _prepareLayout() } /** - * Determine if the pager should be displayed for order items list - * To be called from templates(after _prepareLayout()) + * Determine if the pager should be displayed for order items list. + * + * To be called from templates(after _prepareLayout()). * * @return bool * @since 100.1.7 @@ -101,7 +101,8 @@ public function isPagerDisplayed() /** * Get visible items for current page. - * To be called from templates(after _prepareLayout()) + * + * To be called from templates(after _prepareLayout()). * * @return \Magento\Framework\DataObject[] * @since 100.1.7 @@ -112,8 +113,9 @@ public function getItems() } /** - * Get pager HTML according to our requirements - * To be called from templates(after _prepareLayout()) + * Get pager HTML according to our requirements. + * + * To be called from templates(after _prepareLayout()). * * @return string HTML output * @since 100.1.7 diff --git a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml index e43d32760febb..dc179b6ee4ac1 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml @@ -29,9 +29,10 @@ getItems(); ?> + getParentItem()) continue; ?> - + getItemHtml($item) ?> helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $item) && $item->getGiftMessageId()): ?> helper('Magento\GiftMessage\Helper\Message')->getGiftMessageForEntity($item); ?> @@ -62,8 +63,8 @@ - + isPagerDisplayed()): ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml index 5ecf1ebe893bc..9b3633fde60b4 100644 --- a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml @@ -26,14 +26,18 @@
  1. -
    +
    @@ -46,8 +50,8 @@
-
+
diff --git a/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js b/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js index f393cc3fcd3bc..17e61a77d98a3 100644 --- a/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js +++ b/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js @@ -5,27 +5,43 @@ define([ 'uiComponent', - 'Magento_Customer/js/customer-data' -], function (Component, customerData) { + 'Magento_Customer/js/customer-data', + 'underscore' +], function (Component, customerData, _) { 'use strict'; return Component.extend({ + defaults: { + isShowAddToCart: false + }, + /** @inheritdoc */ initialize: function () { - var isShowAddToCart = false, - item; - this._super(); this.lastOrderedItems = customerData.get('last-ordered-items'); + this.lastOrderedItems.subscribe(this.checkSalableItems.bind(this)); + this.checkSalableItems(); + + return this; + }, + + /** @inheritdoc */ + initObservable: function () { + this._super() + .observe('isShowAddToCart'); + + return this; + }, - for (item in this.lastOrderedItems.items) { - if (item['is_saleable']) { - isShowAddToCart = true; - break; - } - } + /** + * Check if items is_saleable and change add to cart button visibility. + */ + checkSalableItems: function () { + var isShowAddToCart = _.some(this.lastOrderedItems().items, { + 'is_saleable': true + }); - this.lastOrderedItems.isShowAddToCart = isShowAddToCart; + this.isShowAddToCart(isShowAddToCart); } }); }); diff --git a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls index 3915d48681ab1..1234b65a7b910 100644 --- a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls @@ -2,27 +2,38 @@ # See COPYING.txt for license details. type Mutation { - sendEmailToFriend (input: SendEmailToFriendSenderInput): SendEmailToFriendOutput @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendEmailToFriend") @doc(description:"Recommends Product by Sending Single/Multiple Email") + sendEmailToFriend (input: SendEmailToFriendInput): SendEmailToFriendOutput @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendEmailToFriend") @doc(description:"Recommends Product by Sending Single/Multiple Email") } -input SendEmailToFriendSenderInput { - product_id: Int! - sender: Sender! - recipients: [Recipient!]! +input SendEmailToFriendInput { + product_id: Int! + sender: SendEmailToFriendSenderInput! + recipients: [SendEmailToFriendRecipientInput!]! } -type Sender { +input SendEmailToFriendSenderInput { name: String! email: String! message: String! } -type Recipient { +input SendEmailToFriendRecipientInput { name: String! email: String! } type SendEmailToFriendOutput { - sender: Sender - recipients: [Recipient] + sender: SendEmailToFriendSender + recipients: [SendEmailToFriendRecipient] +} + +type SendEmailToFriendSender { + name: String! + email: String! + message: String! +} + +type SendEmailToFriendRecipient { + name: String! + email: String! } 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 48c04458ff49a..f26ca8697c2ff 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 @@ -153,6 +153,11 @@ define([ var itemsType = data.excludeMode ? 'excluded' : 'selected', selections = {}; + if (itemsType === 'excluded' && data.selected && data.selected.length) { + itemsType = 'selected'; + data[itemsType] = _.difference(data.selected, data.excluded); + } + selections[itemsType] = data[itemsType]; if (!selections[itemsType].length) { diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less index c9c97930297cb..40ebb6f3c4569 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less @@ -97,9 +97,11 @@ display: inline-block; font-size: @notifications__font-size; font-weight: @font-weight__bold; + height: 18px; left: 50%; margin-left: .3em; margin-top: -1.1em; + min-width: 18px; padding: .3em .5em; position: absolute; top: 50%; diff --git a/app/design/frontend/Magento/blank/Magento_Banner/web/css/source/_widgets.less b/app/design/frontend/Magento/blank/Magento_Banner/web/css/source/_widgets.less index 80ac77e42fc04..c961d49c5b589 100644 --- a/app/design/frontend/Magento/blank/Magento_Banner/web/css/source/_widgets.less +++ b/app/design/frontend/Magento/blank/Magento_Banner/web/css/source/_widgets.less @@ -8,7 +8,6 @@ // _____________________________________________ & when (@media-common = true) { - .block-banners, .block-banners-inline { &:extend(.abs-margin-for-blocks-and-widgets); @@ -22,7 +21,7 @@ } .banner-item-content { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; img { display: block; diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less index aceccb06d47f7..2468d2a3104e4 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_toolbar.less @@ -4,14 +4,17 @@ // */ // -// Common -// _____________________________________________ +// Variables +// --------------------------------------------- @toolbar-mode-icon-font-size: 24px; @toolbar-element-background: @panel__background-color; -& when (@media-common = true) { +// +// Common +// _____________________________________________ +& when (@media-common = true) { .page-products { .columns { position: relative; @@ -72,12 +75,12 @@ .sorter-action { vertical-align: top; .lib-icon-font( - @icon-arrow-up, - @_icon-font-size: 28px, - @_icon-font-line-height: 32px, - @_icon-font-color: @header-icons-color, - @_icon-font-color-hover: @header-icons-color-hover, - @_icon-font-text-hide: true + @icon-arrow-up, + @_icon-font-size: 28px, + @_icon-font-line-height: 32px, + @_icon-font-color: @header-icons-color, + @_icon-font-color-hover: @header-icons-color-hover, + @_icon-font-text-hide: true ); } @@ -99,7 +102,7 @@ } .limiter-label { - font-weight: 400; + font-weight: @font-weight__regular; } .limiter { @@ -176,11 +179,11 @@ } .lib-icon-font( - @icon-grid, - @_icon-font-size: @toolbar-mode-icon-font-size, - @_icon-font-text-hide: true, - @_icon-font-color: @text__color__muted, - @_icon-font-color-hover: @text__color__muted + @icon-grid, + @_icon-font-size: @toolbar-mode-icon-font-size, + @_icon-font-text-hide: true, + @_icon-font-color: @text__color__muted, + @_icon-font-color-hover: @text__color__muted ); } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less index 7bd27106b67d9..673131563417d 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less @@ -107,7 +107,7 @@ @_toggle-selector: ~'.action.showcart', @_options-selector: ~'.block-minicart', @_dropdown-list-width: 320px, - @_dropdown-list-position-right: 0px, + @_dropdown-list-position-right: 0, @_dropdown-list-pointer-position: right, @_dropdown-list-pointer-position-left-right: 26px, @_dropdown-list-z-index: 101, @@ -148,10 +148,10 @@ .action { &.close { .lib-button-icon( - @icon-remove, - @_icon-font-size: 32px, - @_icon-font-line-height: 32px, - @_icon-font-text-hide: true + @icon-remove, + @_icon-font-size: 32px, + @_icon-font-line-height: 32px, + @_icon-font-text-hide: true ); .lib-button-reset(); height: 40px; @@ -254,12 +254,12 @@ .toggle { .lib-icon-font( - @_icon-font-content: @icon-down, - @_icon-font-size: 28px, - @_icon-font-line-height: 16px, - @_icon-font-text-hide: false, - @_icon-font-position: after, - @_icon-font-display: block + @_icon-font-content: @icon-down, + @_icon-font-size: 28px, + @_icon-font-line-height: 16px, + @_icon-font-text-hide: false, + @_icon-font-position: after, + @_icon-font-display: block ); cursor: pointer; position: relative; @@ -274,8 +274,8 @@ &.active { > .toggle { .lib-icon-font-symbol( - @_icon-font-content: @icon-up, - @_icon-font-position: after + @_icon-font-content: @icon-up, + @_icon-font-position: after ); } } @@ -304,6 +304,7 @@ .weee[data-label] { .lib-font-size(11); + .label { &:extend(.abs-no-display all); } @@ -317,12 +318,12 @@ .product.options { .tooltip.toggle { .lib-icon-font( - @icon-down, - @_icon-font-size: 28px, - @_icon-font-line-height: 28px, - @_icon-font-text-hide: true, - @_icon-font-margin: -3px 0 0 7px, - @_icon-font-position: after + @icon-down, + @_icon-font-size: 28px, + @_icon-font-line-height: 28px, + @_icon-font-text-hide: true, + @_icon-font-margin: -3px 0 0 7px, + @_icon-font-position: after ); .details { @@ -357,19 +358,19 @@ &.edit, &.delete { .lib-icon-font( - @icon-settings, - @_icon-font-size: 28px, - @_icon-font-line-height: 28px, - @_icon-font-text-hide: true, - @_icon-font-color: @color-gray19, - @_icon-font-color-hover: @color-gray19, - @_icon-font-color-active: @color-gray19 + @icon-settings, + @_icon-font-size: 28px, + @_icon-font-line-height: 28px, + @_icon-font-text-hide: true, + @_icon-font-color: @color-gray19, + @_icon-font-color-hover: @color-gray19, + @_icon-font-color-active: @color-gray19 ); } &.delete { .lib-icon-font-symbol( - @_icon-font-content: @icon-trash + @_icon-font-content: @icon-trash ); } } @@ -399,6 +400,7 @@ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .minicart-wrapper { margin-left: 13px; + .block-minicart { right: -15px; width: 390px; 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 145dc10263fb1..2761a2f74f990 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 @@ -185,7 +185,7 @@ } .form-wishlist-search { - .lib-css(margin-bottom, @indent__l*2); + margin-bottom: @indent__l * 2; max-width: 500px; .fieldset { @@ -216,8 +216,9 @@ .block-wishlist-info-items { .block-title { - .lib-css(margin-bottom, @indent__base); .lib-font-size(22); + margin-bottom: @indent__base; + > strong { font-weight: @font-weight__light; } @@ -388,16 +389,16 @@ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .wishlist { &.window.popup { + .field { + .lib-form-field-type-revert(@_type: block); + } + bottom: auto; .lib-css(top, @desktop-popup-position-top); .lib-css(left, @desktop-popup-position-left); .lib-css(margin-left, @desktop-popup-margin-left); .lib-css(width, @desktop-popup-width); right: auto; - - .field { - .lib-form-field-type-revert(@_type: block); - } } } diff --git a/app/design/frontend/Magento/blank/Magento_Review/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Review/web/css/source/_module.less index 7d5b79bc5a091..69ec01d71e104 100644 --- a/app/design/frontend/Magento/blank/Magento_Review/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Review/web/css/source/_module.less @@ -15,9 +15,9 @@ // _____________________________________________ & when (@media-common = true) { - .rating-summary { .lib-rating-summary(); + .rating-result { margin-left: -5px; } @@ -165,10 +165,10 @@ .review-details { .customer-review-rating { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; .item { - .lib-css(margin-bottom, @indent__s); + margin-bottom: @indent__s; &:last-child { margin-bottom: 0; @@ -178,12 +178,12 @@ .review-title { .lib-heading(h3); - .lib-css(font-weight, @font-weight__semibold); - .lib-css(margin-bottom, @indent__base); + font-weight: @font-weight__semibold; + margin-bottom: @indent__base; } .review-content { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; } } @@ -271,7 +271,7 @@ &-field-rating { .control { - margin-bottom: 1.2*@indent__xl; + margin-bottom: 1.2 * @indent__xl; margin-top: @indent__s; } } 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 13da4a4d996cc..5f24c0db8cc88 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -162,7 +162,7 @@ & when (@media-common = true) { .abs-login-block-title { strong { - font-weight: 500; + font-weight: @font-weight__heavier; } .lib-font-size(18); @@ -178,11 +178,11 @@ & when (@media-common = true) { .abs-block-title { + margin-bottom: 15px; + > strong { .lib-heading(h3); } - - margin-bottom: 15px; } } @@ -194,6 +194,7 @@ .abs-account-blocks { .block-title { &:extend(.abs-block-title all); + > .action { margin-left: 15px; } @@ -208,7 +209,7 @@ } > .action { - font-weight: 400; + font-weight: @font-weight__regular; margin-left: @indent__s; } } @@ -232,10 +233,10 @@ & when (@media-common = true) { .abs-dropdown-simple { .lib-dropdown( - @_dropdown-list-item-padding: 5px 5px 5px 23px, - @_dropdown-list-min-width: 200px, - @_icon-font-margin: 0 0 0 5px, - @_icon-font-vertical-align: middle + @_dropdown-list-item-padding: 5px 5px 5px 23px, + @_dropdown-list-min-width: 200px, + @_icon-font-margin: 0 0 0 5px, + @_icon-font-vertical-align: middle ); } } @@ -268,13 +269,13 @@ & when (@media-common = true) { .abs-remove-button-for-blocks { .lib-icon-font( - @icon-remove, - @_icon-font-size: 26px, - @_icon-font-line-height: 15px, - @_icon-font-text-hide: true, - @_icon-font-color: @color-gray19, - @_icon-font-color-hover: @color-gray19, - @_icon-font-color-active: @color-gray19 + @icon-remove, + @_icon-font-size: 26px, + @_icon-font-line-height: 15px, + @_icon-font-text-hide: true, + @_icon-font-color: @color-gray19, + @_icon-font-color-hover: @color-gray19, + @_icon-font-color-active: @color-gray19 ); } } @@ -289,14 +290,14 @@ > a { .lib-link( - @_link-color: @product-name-link__color, - @_link-text-decoration: @product-name-link__text-decoration, - @_link-color-visited: @product-name-link__color__visited, - @_link-text-decoration-visited: @product-name-link__text-decoration__visited, - @_link-color-hover: @product-name-link__color__hover, - @_link-text-decoration-hover: @product-name-link__text-decoration__hover, - @_link-color-active: @product-name-link__color__active, - @_link-text-decoration-active: @product-name-link__text-decoration__active + @_link-color: @product-name-link__color, + @_link-text-decoration: @product-name-link__text-decoration, + @_link-color-visited: @product-name-link__color__visited, + @_link-text-decoration-visited: @product-name-link__text-decoration__visited, + @_link-color-hover: @product-name-link__color__hover, + @_link-text-decoration-hover: @product-name-link__text-decoration__hover, + @_link-color-active: @product-name-link__color__active, + @_link-text-decoration-active: @product-name-link__text-decoration__active ); } } @@ -613,11 +614,11 @@ & when (@media-common = true) { .abs-navigation-icon { .lib-icon-font( - @_icon-font-content: @icon-down, - @_icon-font-size: 34px, - @_icon-font-line-height: 1.2, - @_icon-font-position: after, - @_icon-font-display: block + @_icon-font-content: @icon-down, + @_icon-font-size: 34px, + @_icon-font-line-height: 1.2, + @_icon-font-position: after, + @_icon-font-display: block ); &:after { @@ -635,8 +636,8 @@ & when (@media-common = true) { .abs-split-button { .lib-dropdown-split( - @_options-selector : ~'.items', - @_dropdown-split-button-border-radius-fix: true + @_options-selector : ~'.items', + @_dropdown-split-button-border-radius-fix: true ); vertical-align: middle; } @@ -654,13 +655,13 @@ .abs-actions-addto-gridlist { .lib-icon-font( - @_icon-font-content: '', - @_icon-font-size: 29px, - @_icon-font-color: @addto-color, - @_icon-font-color-hover: @addto-hover-color, - @_icon-font-text-hide: true, - @_icon-font-vertical-align: middle, - @_icon-font-line-height: 24px + @_icon-font-content: '', + @_icon-font-size: 29px, + @_icon-font-color: @addto-color, + @_icon-font-color-hover: @addto-hover-color, + @_icon-font-text-hide: true, + @_icon-font-vertical-align: middle, + @_icon-font-line-height: 24px ); } } @@ -762,11 +763,11 @@ padding-right: 12px; position: relative; .lib-icon-font( - @icon-down, - @_icon-font-size: 26px, - @_icon-font-line-height: 10px, - @_icon-font-margin: 3px 0 0 0, - @_icon-font-position: after + @icon-down, + @_icon-font-size: 26px, + @_icon-font-line-height: 10px, + @_icon-font-margin: 3px 0 0 0, + @_icon-font-position: after ); &:after { @@ -777,16 +778,16 @@ &-expanded { .lib-icon-font-symbol( - @_icon-font-content: @icon-up, - @_icon-font-position: after + @_icon-font-content: @icon-up, + @_icon-font-position: after ); } } .abs-tax-total-expanded { .lib-icon-font-symbol( - @_icon-font-content: @icon-up, - @_icon-font-position: after + @_icon-font-content: @icon-up, + @_icon-font-position: after ); } } @@ -867,10 +868,10 @@ & when (@media-common = true) { .abs-icon-add { .lib-icon-font( - @_icon-font-content: @icon-expand, - @_icon-font-size: 10px, - @_icon-font-line-height: 10px, - @_icon-font-vertical-align: middle + @_icon-font-content: @icon-expand, + @_icon-font-size: 10px, + @_icon-font-line-height: 10px, + @_icon-font-vertical-align: middle ); } } @@ -878,12 +879,12 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .abs-icon-add-mobile { .lib-icon-font( - @_icon-font-content: @icon-expand, - @_icon-font-size: 10px, - @_icon-font-line-height: 10px, - @_icon-font-vertical-align: middle, - @_icon-font-margin: 0 5px 0 0, - @_icon-font-display: block + @_icon-font-content: @icon-expand, + @_icon-font-size: 10px, + @_icon-font-line-height: 10px, + @_icon-font-vertical-align: middle, + @_icon-font-margin: 0 5px 0 0, + @_icon-font-display: block ); } } @@ -924,11 +925,11 @@ position: relative; .lib-icon-font( - @_icon-font-content: @icon-down, - @_icon-font-size: 28px, - @_icon-font-text-hide: false, - @_icon-font-position: after, - @_icon-font-display: block + @_icon-font-content: @icon-down, + @_icon-font-size: 28px, + @_icon-font-text-hide: false, + @_icon-font-position: after, + @_icon-font-display: block ); &:after { @@ -1068,12 +1069,12 @@ font-weight: @font-weight__bold; .lib-link-as-button(); .lib-button( - @_button-padding: 7px 15px 7px 0, - @_button-icon-use: true, - @_button-font-content: @icon-prev, - @_button-icon-font-size: 32px, - @_button-icon-font-line-height: 16px, - @_button-icon-font-position: before + @_button-padding: 7px 15px 7px 0, + @_button-icon-use: true, + @_button-font-content: @icon-prev, + @_button-icon-font-size: 32px, + @_button-icon-font-line-height: 16px, + @_button-icon-font-position: before ); &:active { @@ -1083,9 +1084,9 @@ &.update { .lib-button-icon( - @icon-update, - @_icon-font-size: 32px, - @_icon-font-line-height: 16px + @icon-update, + @_icon-font-size: 32px, + @_icon-font-line-height: 16px ); padding-left: @indent__xs; } @@ -1160,7 +1161,7 @@ & when (@media-common = true) { .abs-field-date-input { - .lib-css(margin-right, @indent__s); + margin-right: @indent__s; width: calc(~'100% -' @icon-calendar__font-size + @indent__s); } } @@ -1175,7 +1176,7 @@ position: relative; input { - .lib-css(margin-right, @indent__s); + margin-right: @indent__s; width: calc(~'100% -' @checkout-tooltip-icon__font-size + @indent__s + @indent__xs); } } @@ -1193,12 +1194,12 @@ &:before, &:after { .lib-arrow( - @_position: top, - @_size: @checkout-tooltip-icon-arrow__font-size, - @_color: @checkout-tooltip-content__background-color + @_position: top, + @_size: @checkout-tooltip-icon-arrow__font-size, + @_color: @checkout-tooltip-content__background-color ); .lib-css(margin-top, @checkout-tooltip-icon-arrow__left); - .lib-css(right, @indent__s); + right: @indent__s; left: auto; top: 0; } @@ -1234,11 +1235,11 @@ .lib-css(border-bottom, @checkout-step-title__border); .lib-css(padding-bottom, @checkout-step-title__padding); .lib-typography( - @_font-size: @checkout-step-title__font-size, - @_font-weight: @checkout-step-title__font-weight, - @_font-family: false, - @_font-style: false, - @_line-height: false + @_font-size: @checkout-step-title__font-size, + @_font-weight: @checkout-step-title__font-weight, + @_font-family: false, + @_font-style: false, + @_line-height: false ); } } @@ -1299,11 +1300,11 @@ .amount .price { .lib-icon-font( - @icon-down, - @_icon-font-size: 30px, - @_icon-font-text-hide: true, - @_icon-font-position: after, - @_icon-font-display: block + @icon-down, + @_icon-font-size: 30px, + @_icon-font-text-hide: true, + @_icon-font-position: after, + @_icon-font-display: block ); padding-right: @indent__m; position: relative; @@ -1323,16 +1324,16 @@ .amount .price { .lib-icon-font-symbol( - @_icon-font-content: @icon-up, - @_icon-font-position: after + @_icon-font-content: @icon-up, + @_icon-font-position: after ); } } } &-details { - display: none; .lib-css(border-bottom, @border-width__base solid @border-color__base); + display: none; &.shown { display: table-row; @@ -1357,10 +1358,10 @@ cursor: pointer; font-weight: @font-weight__semibold; .lib-icon-font( - @_icon-font-content: @icon-down, - @_icon-font-size: 30px, - @_icon-font-position: after, - @_icon-font-display: block + @_icon-font-content: @icon-down, + @_icon-font-size: 30px, + @_icon-font-position: after, + @_icon-font-display: block ); margin-bottom: 0; overflow: hidden; @@ -1388,8 +1389,8 @@ &.active { > .title { .lib-icon-font-symbol( - @_icon-font-content: @icon-up, - @_icon-font-position: after + @_icon-font-content: @icon-up, + @_icon-font-position: after ); } diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less index 03e4bfff3ba9c..9576004421e48 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less @@ -107,23 +107,23 @@ .minicart-wrapper { .lib-dropdown( - @_toggle-selector: ~'.action.showcart', - @_options-selector: ~'.block-minicart', - @_dropdown-list-width: 320px, - @_dropdown-list-position-right: 0px, - @_dropdown-list-pointer-position: right, - @_dropdown-list-pointer-position-left-right: 26px, - @_dropdown-list-z-index: 101, - @_dropdown-toggle-icon-content: @icon-cart, - @_dropdown-toggle-active-icon-content: @icon-cart, - @_dropdown-list-item-padding: false, - @_dropdown-list-item-hover: false, - @_icon-font-position: before, - @_icon-font-size: 22px, - @_icon-font-line-height: 28px, - @_icon-font-color: @minicart-icons-color, - @_icon-font-color-hover: @minicart-icons-color-hover, - @_icon-font-color-active: @minicart-icons-color + @_toggle-selector: ~'.action.showcart', + @_options-selector: ~'.block-minicart', + @_dropdown-list-width: 320px, + @_dropdown-list-position-right: 0, + @_dropdown-list-pointer-position: right, + @_dropdown-list-pointer-position-left-right: 26px, + @_dropdown-list-z-index: 101, + @_dropdown-toggle-icon-content: @icon-cart, + @_dropdown-toggle-active-icon-content: @icon-cart, + @_dropdown-list-item-padding: false, + @_dropdown-list-item-hover: false, + @_icon-font-position: before, + @_icon-font-size: 22px, + @_icon-font-line-height: 28px, + @_icon-font-color: @minicart-icons-color, + @_icon-font-color-hover: @minicart-icons-color-hover, + @_icon-font-color-active: @minicart-icons-color ); float: right; @@ -226,6 +226,7 @@ .minicart-items { .lib-list-reset-styles(); + .product-item { padding: @indent__base 0; @@ -297,13 +298,14 @@ .toggle { &:extend(.abs-toggling-title all); + border: 0; + padding: 0 @indent__xl @indent__xs 0; + &:after { .lib-css(color, @color-gray56); margin: 0 0 0 @indent__xs; position: static; } - border: 0; - padding: 0 @indent__xl @indent__xs 0; } .active { @@ -323,14 +325,15 @@ .toggle { &.tooltip { .lib-icon-font( - @icon-down, - @_icon-font-size: 12px, - @_icon-font-line-height: 12px, - @_icon-font-text-hide: true, - @_icon-font-margin: -3px 0 0 7px, - @_icon-font-position: after + @icon-down, + @_icon-font-size: 12px, + @_icon-font-line-height: 12px, + @_icon-font-text-hide: true, + @_icon-font-margin: -3px 0 0 7px, + @_icon-font-position: after ); } + > span { &:extend(.abs-visually-hidden-reset all); } @@ -368,19 +371,19 @@ &.edit, &.delete { .lib-icon-font( - @icon-edit, - @_icon-font-size: 18px, - @_icon-font-line-height: 20px, - @_icon-font-text-hide: true, - @_icon-font-color: @minicart-icons-color, - @_icon-font-color-hover: @primary__color, - @_icon-font-color-active: @minicart-icons-color + @icon-edit, + @_icon-font-size: 18px, + @_icon-font-line-height: 20px, + @_icon-font-text-hide: true, + @_icon-font-color: @minicart-icons-color, + @_icon-font-color-hover: @primary__color, + @_icon-font-color-active: @minicart-icons-color ); } &.delete { .lib-icon-font-symbol( - @_icon-font-content: @icon-trash + @_icon-font-content: @icon-trash ); } } 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 6ab7a8e47a174..0b01c54a64378 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,7 @@ .items { padding: 6px 0; text-align: left; + .item { > span { display: block; @@ -47,6 +48,7 @@ } } + li { padding: 0; } @@ -115,6 +117,7 @@ &.split, &.toggle { .lib-css(color, @link__color); + &:before { display: none; } @@ -135,6 +138,7 @@ &.overlay { .lib-window-overlay(); + &.active { display: block; } @@ -254,7 +258,7 @@ } .form-wishlist-search { - .lib-css(margin-bottom, @indent__l*2); + margin-bottom: @indent__l * 2; max-width: 500px; .fieldset { @@ -281,7 +285,7 @@ .block-wishlist-info-items { .block-title { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; .lib-font-size(22px); > strong { @@ -352,6 +356,7 @@ // Select wish list &-select { margin: 0 -@layout__width-xs-indent 20px; + .wishlist-name { .lib-font-size(16); &:extend(.abs-toggling-title-mobile all); diff --git a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less index da78406f92212..2c66420f65fbd 100644 --- a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less @@ -14,7 +14,6 @@ // _____________________________________________ & when (@media-common = true) { - .data.switch .counter { .lib-css(color, @text__color__muted); @@ -118,13 +117,13 @@ strong { display: block; - font-weight: 600; + font-weight: @font-weight__semibold; } } .fieldset &-field-ratings { > .label { - font-weight: 600; + font-weight: @font-weight__semibold; margin-bottom: @indent__s; padding: 0; } @@ -140,11 +139,11 @@ &-field-rating { .label { - font-weight: 600; + font-weight: @font-weight__semibold; } .control { - margin-bottom: 1.2*@indent__xl; + margin-bottom: 1.2 * @indent__xl; margin-top: @indent__s; } } @@ -181,7 +180,7 @@ display: inline; .review-details-value { - font-weight: 400; + font-weight: @font-weight__regular; } } @@ -341,7 +340,7 @@ .block-reviews-dashboard { .items { .item { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; &:last-child { margin-bottom: 0; @@ -353,14 +352,14 @@ display: inline-block; &:not(:last-child) { - .lib-css(margin-bottom, @indent__xs); + margin-bottom: @indent__xs; } } .rating-summary { .label { - .lib-css(font-weight, @font-weight__semibold); - .lib-css(margin-right, @indent__s); + font-weight: @font-weight__semibold; + margin-right: @indent__s; } } } @@ -368,7 +367,7 @@ .table-reviews, .block-reviews-dashboard { .product-name { - .lib-css(font-weight, @font-weight__regular); + font-weight: @font-weight__regular; } } @@ -416,6 +415,7 @@ .product-details { &:extend(.abs-add-clearfix all); &:extend(.abs-margin-for-blocks-and-widgets all); + .rating-average-label { &:extend(.abs-visually-hidden all); } @@ -436,9 +436,10 @@ } .customer-review-rating { - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; + .item { - .lib-css(margin-bottom, @indent__s); + margin-bottom: @indent__s; &:last-child { margin-bottom: 0; @@ -450,13 +451,13 @@ .review-title { .lib-heading(h3); - .lib-css(font-weight, @font-weight__semibold); - .lib-css(margin-bottom, @indent__base); + font-weight: @font-weight__semibold; + margin-bottom: @indent__base; } .review-content { margin: 0; - .lib-css(margin-bottom, @indent__base); + margin-bottom: @indent__base; } .review-date { @@ -473,13 +474,13 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) { .customer-review { .product-name { - .lib-css(margin-bottom, @indent__xs); + margin-bottom: @indent__xs; } .product-reviews-summary { .rating-summary { display: block; - .lib-css(margin-bottom, @indent__xs); + margin-bottom: @indent__xs; } } } diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less index a0f734b05cbd1..1e4a92fa0701f 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less @@ -612,10 +612,6 @@ padding: 25px; .col { - &.name { - padding-left: 0; - } - &.price { text-align: center; } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index 64ad44528572d..5458b5cfbb731 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -29,8 +29,6 @@ class Client private $json; /** - * CurlClient constructor. - * * @param CurlClient|null $curlClient * @param JsonSerializer|null $json */ @@ -81,6 +79,8 @@ public function postQuery(string $query, array $variables = [], string $operatio } /** + * Process errors + * * @param array $responseBodyArray * @throws \Exception */ @@ -102,13 +102,18 @@ private function processErrors($responseBodyArray) } } - throw new \Exception('GraphQL response contains errors: ' . $errorMessage); + throw new ResponseContainsErrorsException( + 'GraphQL response contains errors: ' . $errorMessage, + $responseBodyArray + ); } throw new \Exception('GraphQL responded with an unknown error: ' . json_encode($responseBodyArray)); } } /** + * Get endpoint url + * * @return string resource URL * @throws \Exception */ diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/ResponseContainsErrorsException.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/ResponseContainsErrorsException.php new file mode 100644 index 0000000000000..568de57543d84 --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/ResponseContainsErrorsException.php @@ -0,0 +1,41 @@ +responseData = $responseData; + } + + /** + * Get response data + * + * @return array + */ + public function getResponseData(): array + { + return $this->responseData; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php index d8e06fd08385c..57f526b1cb2f7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php @@ -7,43 +7,45 @@ namespace Magento\GraphQl\Cms; -use Magento\Cms\Model\Block; -use Magento\Cms\Model\GetBlockByIdentifier; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Cms\Api\BlockRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Widget\Model\Template\FilterEmulate; class CmsBlockTest extends GraphQlAbstract { /** - * @var \Magento\TestFramework\ObjectManager + * @var BlockRepositoryInterface */ - private $objectManager; + private $blockRepository; + + /** + * @var FilterEmulate + */ + private $filterEmulate; protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->blockRepository = Bootstrap::getObjectManager()->get(BlockRepositoryInterface::class); + $this->filterEmulate = Bootstrap::getObjectManager()->get(FilterEmulate::class); } /** * Verify the fields of CMS Block selected by identifiers * - * @magentoApiDataFixture Magento/Cms/_files/block.php + * @magentoApiDataFixture Magento/Cms/_files/blocks.php */ - public function testGetCmsBlocksByIdentifiers() + public function testGetCmsBlock() { - /** @var StoreManagerInterface $storeManager */ - $storeManager = $this->objectManager->get(StoreManagerInterface::class); - $storeId = (int)$storeManager->getStore()->getId(); - $cmsBlock = $this->objectManager->get(GetBlockByIdentifier::class)->execute("fixture_block", $storeId); + $cmsBlock = $this->blockRepository->getById('enabled_block'); $cmsBlockData = $cmsBlock->getData(); - /** @var FilterEmulate $widgetFilter */ - $widgetFilter = $this->objectManager->get(FilterEmulate::class); - $renderedContent = $widgetFilter->setUseSessionInUrl(false)->filter($cmsBlock->getContent()); + $renderedContent = $this->filterEmulate->setUseSessionInUrl(false)->filter($cmsBlock->getContent()); + $query = <<graphQlQuery($query); - $this->assertArrayHasKey('cmsBlocks', $response); - $this->assertArrayHasKey('items', $response['cmsBlocks']); - $this->assertArrayHasKey('content', $response['cmsBlocks']['items'][0]); - $this->assertEquals($cmsBlockData['identifier'], $response['cmsBlocks']['items'][0]['identifier']); - $this->assertEquals($cmsBlockData['title'], $response['cmsBlocks']['items'][0]['title']); - $this->assertEquals($renderedContent, $response['cmsBlocks']['items'][0]['content']); + + self::assertArrayHasKey('cmsBlocks', $response); + self::assertArrayHasKey('items', $response['cmsBlocks']); + + self::assertEquals($cmsBlockData['identifier'], $response['cmsBlocks']['items'][0]['identifier']); + self::assertEquals($cmsBlockData['title'], $response['cmsBlocks']['items'][0]['title']); + self::assertEquals($renderedContent, $response['cmsBlocks']['items'][0]['content']); } /** * Verify the message when CMS Block is disabled * - * @magentoApiDataFixture Magento/Cms/_files/block.php + * @expectedException \Exception + * @expectedExceptionMessage The CMS block with the "disabled_block" ID doesn't exist + * + * @magentoApiDataFixture Magento/Cms/_files/blocks.php */ - public function testGetDisabledCmsBlockByIdentifiers() + public function testGetDisabledCmsBlock() { - /** @var StoreManagerInterface $storeManager */ - $storeManager = $this->objectManager->get(StoreManagerInterface::class); - $storeId = (int)$storeManager->getStore()->getId(); - $cmsBlockId = $this->objectManager->get(GetBlockByIdentifier::class) - ->execute("fixture_block", $storeId) - ->getId(); - $this->objectManager->get(Block::class)->load($cmsBlockId)->setIsActive(0)->save(); $query = <<expectException(\Exception::class); - $this->expectExceptionMessage('No such entity.'); $this->graphQlQuery($query); } /** * Verify the message when identifiers were not specified + * + * @expectedException \Exception + * @expectedExceptionMessage "identifiers" of CMS blocks should be specified */ - public function testGetCmsBlockBypassingIdentifiers() + public function testGetCmsBlocksWithoutIdentifiers() { $query = <<expectException(\Exception::class); - $this->expectExceptionMessage('"identifiers" of CMS blocks should be specified'); $this->graphQlQuery($query); } /** * Verify the message when CMS Block with such identifiers does not exist + * + * @expectedException \Exception + * @expectedExceptionMessage The CMS block with the "nonexistent_id" ID doesn't exist. */ public function testGetCmsBlockByNonExistentIdentifier() { $query = <<expectException(\Exception::class); - $this->expectExceptionMessage('The CMS block with the "0" ID doesn\'t exist.'); $this->graphQlQuery($query); } + + /** + * Verify the fields of CMS Block selected by identifiers + * + * @magentoApiDataFixture Magento/Cms/_files/blocks.php + */ + public function testGetEnabledAndDisabledCmsBlockInOneRequest() + { + $query = + <<graphQlQuery($query); + self::fail('Response should contains errors.'); + } catch (ResponseContainsErrorsException $e) { + $responseData = $e->getResponseData(); + } + + self::assertNotEmpty($responseData); + self::assertEquals('enabled_block', $responseData['data']['cmsBlocks']['items'][0]['identifier']); + self::assertEquals( + 'The CMS block with the "disabled_block" ID doesn\'t exist.', + $responseData['errors'][0]['message'] + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/Captcha/Observer/CaseCheckOnFrontendUnsuccessfulMessageWhenCaptchaFailedTest.php b/dev/tests/integration/testsuite/Magento/Captcha/Observer/CaseCheckOnFrontendUnsuccessfulMessageWhenCaptchaFailedTest.php new file mode 100644 index 0000000000000..8355d81fdf5d9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Captcha/Observer/CaseCheckOnFrontendUnsuccessfulMessageWhenCaptchaFailedTest.php @@ -0,0 +1,118 @@ +_objectManager->get(FormKey::class); + $post = [ + 'login' => [ + 'username' => 'dummy@dummy.com', + 'password' => 'dummy_password1', + ], + 'captcha' => ['user_login' => 'wrong_captcha'], + 'form_key' => $formKey->getFormKey(), + ]; + + $this->prepareRequestData($post); + + $this->dispatch('customer/account/loginPost'); + + $this->assertRedirect($this->stringContains('customer/account/login')); + $this->assertSessionMessages( + $this->equalTo(['Incorrect CAPTCHA']), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Test incorrect captcha on customer forgot password page + * + * @codingStandardsIgnoreStart + * @magentoConfigFixture current_store customer/password/limit_password_reset_requests_method 0 + * @magentoConfigFixture default_store customer/captcha/enable 1 + * @magentoConfigFixture default_store customer/captcha/forms user_forgotpassword + * @magentoConfigFixture default_store customer/captcha/mode always + */ + public function testForgotPasswordCheckUnsuccessfulMessageWhenCaptchaFailed() + { + $post = ['email' => 'dummy@dummy.com']; + $this->prepareRequestData($post); + + $this->dispatch('customer/account/forgotPasswordPost'); + + $this->assertRedirect($this->stringContains('customer/account/forgotpassword')); + $this->assertSessionMessages( + $this->equalTo(['Incorrect CAPTCHA']), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Test incorrect captcha on customer create account page + * + * @codingStandardsIgnoreStart + * @magentoConfigFixture current_store customer/password/limit_password_reset_requests_method 0 + * @magentoConfigFixture default_store customer/captcha/enable 1 + * @magentoConfigFixture default_store customer/captcha/forms user_create + * @magentoConfigFixture default_store customer/captcha/mode always + */ + public function testCreateAccountCheckUnsuccessfulMessageWhenCaptchaFailed() + { + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $post = [ + 'firstname' => 'Firstname', + 'lastname' => 'Lastname', + 'email' => 'dummy@dummy.com', + 'password' => 'TestPassword123', + 'password_confirmation' => 'TestPassword123', + 'captcha' => ['user_create' => 'wrong_captcha'], + 'form_key' => $formKey->getFormKey(), + ]; + $this->prepareRequestData($post); + + $this->dispatch('customer/account/createPost'); + + $this->assertRedirect($this->stringContains('customer/account/create')); + $this->assertSessionMessages( + $this->equalTo(['Incorrect CAPTCHA']), + MessageInterface::TYPE_ERROR + ); + } + + /** + * @param array $postData + * @return void + */ + private function prepareRequestData($postData) + { + $this->getRequest()->setMethod(Request::METHOD_POST); + $this->getRequest()->setPostValue($postData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/blocks.php b/dev/tests/integration/testsuite/Magento/Cms/_files/blocks.php new file mode 100644 index 0000000000000..bbdc697645a42 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/blocks.php @@ -0,0 +1,52 @@ +get(BlockRepositoryInterface::class); +/** @var BlockInterfaceFactory $blockFactory */ +$blockFactory = Bootstrap::getObjectManager()->get(BlockInterfaceFactory::class); +$storeId = Bootstrap::getObjectManager()->get(StoreManagerInterface::class)->getStore()->getId(); + +/** @var BlockInterface $block */ +$block = $blockFactory->create([ + 'data' => [ + BlockInterface::IDENTIFIER => 'enabled_block', + BlockInterface::TITLE => 'Enabled CMS Block Title', + BlockInterface::CONTENT => ' +

Enabled Block

+
store url +

Config value: "{{config path="web/unsecure/base_url"}}".

+

Custom variable: "{{customvar code="variable_code"}}".

+ ', + BlockInterface::IS_ACTIVE => 1, + 'store_id' => [$storeId], + ] +]); +$blockRepository->save($block); + +/** @var BlockInterface $block */ +$block = $blockFactory->create([ + 'data' => [ + BlockInterface::IDENTIFIER => 'disabled_block', + BlockInterface::TITLE => 'Disabled CMS Block Title', + BlockInterface::CONTENT => ' +

Disabled Block

+ store url +

Config value: "{{config path="web/unsecure/base_url"}}".

+

Custom variable: "{{customvar code="variable_code"}}".

+ ', + BlockInterface::IS_ACTIVE => 0, + 'store_id' => [$storeId], + ] +]); +$blockRepository->save($block); diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/blocks_rollback.php b/dev/tests/integration/testsuite/Magento/Cms/_files/blocks_rollback.php new file mode 100644 index 0000000000000..a7a2a51524cf0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/blocks_rollback.php @@ -0,0 +1,23 @@ +get(BlockRepositoryInterface::class); + +foreach (['enabled_block', 'disabled_block'] as $blockId) { + try { + $blockRepository->deleteById($blockId); + } catch (NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractTest.php b/dev/tests/integration/testsuite/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractTest.php index 419a7d04b778a..2ee0953dcdbf1 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractTest.php @@ -72,4 +72,42 @@ public function testGetAllIdsWithBind() $this->_model->addBindParam('code', 'admin'); $this->assertEquals(['0'], $this->_model->getAllIds()); } + + /** + * Check add field to select doesn't remove expression field from select. + * + * @return void + */ + public function testAddExpressionFieldToSelectWithAdditionalFields() + { + $expectedColumns = ['code', 'test_field']; + $actualColumns = []; + + $testExpression = new \Zend_Db_Expr('(sort_order + group_id)'); + $this->_model->addExpressionFieldToSelect('test_field', $testExpression, ['sort_order', 'group_id']); + $this->_model->addFieldToSelect('code', 'code'); + $columns = $this->_model->getSelect()->getPart(\Magento\Framework\DB\Select::COLUMNS); + foreach ($columns as $columnEntry) { + $actualColumns[] = $columnEntry[2]; + } + + $this->assertEquals($expectedColumns, $actualColumns); + } + + /** + * Check add expression field doesn't remove all fields from select. + * + * @return void + */ + public function testAddExpressionFieldToSelectWithoutAdditionalFields() + { + $expectedColumns = ['*', 'test_field']; + + $testExpression = new \Zend_Db_Expr('(sort_order + group_id)'); + $this->_model->addExpressionFieldToSelect('test_field', $testExpression, ['sort_order', 'group_id']); + $columns = $this->_model->getSelect()->getPart(\Magento\Framework\DB\Select::COLUMNS); + $actualColumns = [$columns[0][1], $columns[1][2]]; + + $this->assertEquals($expectedColumns, $actualColumns); + } } diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php index 78732d73456f7..3c6d2b7321b82 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php @@ -9,6 +9,9 @@ use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\ValidatorException; +/** + * Write Interface implementation + */ class Write extends Read implements WriteInterface { /** @@ -175,6 +178,7 @@ public function createSymlink($path, $destination, WriteInterface $targetDirecto */ public function delete($path = null) { + $exceptionMessages = []; $this->validatePath($path); if (!$this->isExist($path)) { return true; @@ -183,11 +187,59 @@ public function delete($path = null) if ($this->driver->isFile($absolutePath)) { $this->driver->deleteFile($absolutePath); } else { - $this->driver->deleteDirectory($absolutePath); + try { + $this->deleteFilesRecursively($absolutePath); + } catch (FileSystemException $e) { + $exceptionMessages[] = $e->getMessage(); + } + try { + $this->driver->deleteDirectory($absolutePath); + } catch (FileSystemException $e) { + $exceptionMessages[] = $e->getMessage(); + } + + if (!empty($exceptionMessages)) { + throw new FileSystemException( + new \Magento\Framework\Phrase( + \implode(' ', $exceptionMessages) + ) + ); + } } return true; } + /** + * Delete files recursively + * + * Implemented in order to delete as much files as possible and collect all exceptions + * + * @param string $path + * @return void + * @throws FileSystemException + */ + private function deleteFilesRecursively(string $path) + { + $exceptionMessages = []; + $entitiesList = $this->driver->readDirectoryRecursively($path); + foreach ($entitiesList as $entityPath) { + if ($this->driver->isFile($entityPath)) { + try { + $this->driver->deleteFile($entityPath); + } catch (FileSystemException $e) { + $exceptionMessages[] = $e->getMessage(); + } + } + } + if (!empty($exceptionMessages)) { + throw new FileSystemException( + new \Magento\Framework\Phrase( + \implode(' ', $exceptionMessages) + ) + ); + } + } + /** * Change permissions of given path * @@ -245,7 +297,7 @@ public function touch($path, $modificationTime = null) /** * Check if given path is writable * - * @param null $path + * @param string|null $path * @return bool * @throws \Magento\Framework\Exception\FileSystemException * @throws ValidatorException diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/File.php b/lib/internal/Magento/Framework/Filesystem/Driver/File.php index 499d1f5adc9f3..59c9775d73a0a 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/File.php @@ -400,24 +400,36 @@ public function deleteFile($path) */ public function deleteDirectory($path) { + $exceptionMessages = []; $flags = \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS; $iterator = new \FilesystemIterator($path, $flags); /** @var \FilesystemIterator $entity */ foreach ($iterator as $entity) { - if ($entity->isDir()) { - $this->deleteDirectory($entity->getPathname()); - } else { - $this->deleteFile($entity->getPathname()); + try { + if ($entity->isDir()) { + $this->deleteDirectory($entity->getPathname()); + } else { + $this->deleteFile($entity->getPathname()); + } + } catch (FileSystemException $exception) { + $exceptionMessages[] = $exception->getMessage(); } } + if (!empty($exceptionMessages)) { + throw new FileSystemException( + new \Magento\Framework\Phrase( + \implode(' ', $exceptionMessages) + ) + ); + } + $fullPath = $this->getScheme() . $path; if (is_link($fullPath)) { $result = @unlink($fullPath); } else { $result = @rmdir($fullPath); } - if (!$result) { throw new FileSystemException( new \Magento\Framework\Phrase( diff --git a/lib/internal/Magento/Framework/Locale/Config.php b/lib/internal/Magento/Framework/Locale/Config.php index 8767bd515db82..fe685fdf35b73 100644 --- a/lib/internal/Magento/Framework/Locale/Config.php +++ b/lib/internal/Magento/Framework/Locale/Config.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Locale; +/** + * Allowed locale and currency configuration. + */ class Config implements \Magento\Framework\Locale\ConfigInterface { /** @@ -90,6 +93,7 @@ class Config implements \Magento\Framework\Locale\ConfigInterface 'sq_AL', /*Albanian (Albania)*/ 'sr_Cyrl_RS', /*Serbian (Serbia)*/ 'sv_SE', /*Swedish (Sweden)*/ + 'sv_FI', /*Swedish (Finland)*/ 'sw_KE', /*Swahili (Kenya)*/ 'th_TH', /*Thai (Thailand)*/ 'tr_TR', /*Turkish (Turkey)*/ diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php index b57755ed7eafa..8ec47ed97e11c 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php @@ -45,6 +45,13 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn */ protected $_fieldsToSelect = null; + /** + * Expression fields to select in query. + * + * @var array + */ + private $expressionFieldsToSelect = []; + /** * Fields initial fields to select like id_field * @@ -170,7 +177,7 @@ public function setMainTable($table) } /** - * {@inheritdoc} + * @inheritdoc */ protected function _initSelect() { @@ -205,7 +212,7 @@ protected function _initSelectFields() $columnsToSelect = []; foreach ($columns as $columnEntry) { list($correlationName, $column, $alias) = $columnEntry; - if ($correlationName !== 'main_table') { + if ($correlationName !== 'main_table' || isset($this->expressionFieldsToSelect[$alias])) { // Add joined fields to select if ($column instanceof \Zend_Db_Expr) { $column = $column->__toString(); @@ -347,6 +354,7 @@ public function addExpressionFieldToSelect($alias, $expression, $fields) } $this->getSelect()->columns([$alias => $fullExpression]); + $this->expressionFieldsToSelect[$alias] = $fullExpression; return $this; } diff --git a/lib/internal/Magento/Framework/View/Layout/Generator/Block.php b/lib/internal/Magento/Framework/View/Layout/Generator/Block.php index bc250c54e7946..39da65a2d1f3d 100755 --- a/lib/internal/Magento/Framework/View/Layout/Generator/Block.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/Block.php @@ -6,6 +6,7 @@ namespace Magento\Framework\View\Layout\Generator; use Magento\Framework\App\State; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\ObjectManager\Config\Reader\Dom; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Layout; @@ -102,9 +103,7 @@ public function __construct( } /** - * {@inheritdoc} - * - * @return string + * @inheritdoc */ public function getType() { @@ -272,7 +271,7 @@ protected function getBlockInstance($block, array $arguments = []) } } if (!$block instanceof \Magento\Framework\View\Element\AbstractBlock) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( new \Magento\Framework\Phrase( 'Invalid block type: %1', [is_object($block) ? get_class($block) : (string) $block] diff --git a/lib/web/css/source/components/_modals.less b/lib/web/css/source/components/_modals.less index fb29a74c60585..a8e8cebde3a6c 100644 --- a/lib/web/css/source/components/_modals.less +++ b/lib/web/css/source/components/_modals.less @@ -103,6 +103,10 @@ &.confirm { .modal-inner-wrap { .lib-css(width, @modal-popup-confirm__width); + + .modal-content { + padding-right: 7rem; + } } } diff --git a/lib/web/mage/collapsible.js b/lib/web/mage/collapsible.js index 69e576bba604a..3d283d102e323 100644 --- a/lib/web/mage/collapsible.js +++ b/lib/web/mage/collapsible.js @@ -63,6 +63,12 @@ define([ this.icons = true; } + this.element.on('dimensionsChanged', function (e) { + if (e.target && e.target.classList.contains('active')) { + this._scrollToTopIfVisible(e.target); + } + }.bind(this)); + this._bind('click'); this._trigger('created'); }, @@ -453,9 +459,6 @@ define([ if (this.options.animate) { this._animate(showProps); } else { - if (this.content.length) { - this._scrollToTopIfVisible(this.content.get(0).parentElement); - } this.content.show(); } this._open(); diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index e7e04a26b29c1..b441477ab8d8a 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -83,7 +83,7 @@ define([ /** * When the widget gets instantiated, the first tab that is not disabled receive focusable property - * Updated: for accessibility all tabs receive tabIndex 0 + * All tabs receive tabIndex 0 * @private */ _processTabIndex: function () { @@ -91,17 +91,8 @@ define([ self.triggers.attr('tabIndex', 0); $.each(this.collapsibles, function (i) { - if (!$(this).collapsible('option', 'disabled')) { - self.triggers.eq(i).attr('tabIndex', 0); - - return false; - } - }); - $.each(this.collapsibles, function (i) { - $(this).on('beforeOpen', function () { - self.triggers.attr('tabIndex', 0); - self.triggers.eq(i).attr('tabIndex', 0); - }); + self.triggers.attr('tabIndex', 0); + self.triggers.eq(i).attr('tabIndex', 0); }); }, diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php index 2f19f010c1704..173064b472217 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php @@ -27,12 +27,14 @@ class Cache implements ConfigOptionsListInterface 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 INPUT_KEY_CACHE_ID_PREFIX = 'cache-id-prefix'; 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'; + const CONFIG_PATH_CACHE_ID_PREFIX = 'cache/frontend/default/id_prefix'; /** * @var array @@ -77,7 +79,7 @@ public function __construct(RedisConnectionValidator $redisValidator) } /** - * {@inheritdoc} + * @inheritdoc */ public function getOptions() { @@ -112,6 +114,12 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, 'Redis server password' + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_ID_PREFIX, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_ID_PREFIX, + 'ID prefix for cache keys' ) ]; } @@ -122,6 +130,11 @@ public function getOptions() public function createConfig(array $options, DeploymentConfig $deploymentConfig) { $configData = new ConfigData(ConfigFilePool::APP_ENV); + if (isset($options[self::INPUT_KEY_CACHE_ID_PREFIX])) { + $configData->set(self::CONFIG_PATH_CACHE_ID_PREFIX, $options[self::INPUT_KEY_CACHE_ID_PREFIX]); + } else { + $configData->set(self::CONFIG_PATH_CACHE_ID_PREFIX, $this->generateCachePrefix()); + } if (isset($options[self::INPUT_KEY_CACHE_BACKEND])) { if ($options[self::INPUT_KEY_CACHE_BACKEND] == self::INPUT_VALUE_CACHE_REDIS) { @@ -241,4 +254,14 @@ private function getDefaultConfigValue($inputKey) return ''; } } + + /** + * Generate default cache ID prefix based on installation dir + * + * @return string + */ + private function generateCachePrefix(): string + { + return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; + } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php index c288b4dd51d65..7451b59356828 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php @@ -28,6 +28,7 @@ class PageCache implements ConfigOptionsListInterface 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 INPUT_KEY_PAGE_CACHE_ID_PREFIX = 'page-cache-id-prefix'; 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'; @@ -35,6 +36,7 @@ class PageCache implements ConfigOptionsListInterface 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'; + const CONFIG_PATH_PAGE_CACHE_ID_PREFIX = 'cache/frontend/page_cache/id_prefix'; /** * @var array @@ -81,7 +83,7 @@ public function __construct(RedisConnectionValidator $redisValidator) } /** - * {@inheritdoc} + * @inheritdoc */ public function getOptions() { @@ -122,6 +124,12 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, 'Redis server password' + ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_ID_PREFIX, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, + 'ID prefix for cache keys' ) ]; } @@ -132,6 +140,11 @@ public function getOptions() public function createConfig(array $options, DeploymentConfig $deploymentConfig) { $configData = new ConfigData(ConfigFilePool::APP_ENV); + if (isset($options[self::INPUT_KEY_PAGE_CACHE_ID_PREFIX])) { + $configData->set(self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, $options[self::INPUT_KEY_PAGE_CACHE_ID_PREFIX]); + } else { + $configData->set(self::CONFIG_PATH_PAGE_CACHE_ID_PREFIX, $this->generateCachePrefix()); + } if (isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND])) { if ($options[self::INPUT_KEY_PAGE_CACHE_BACKEND] == self::INPUT_VALUE_PAGE_CACHE_REDIS) { @@ -252,4 +265,14 @@ private function getDefaultConfigValue($inputKey) return ''; } } + + /** + * Generate default cache ID prefix based on installation dir + * + * @return string + */ + private function generateCachePrefix(): string + { + return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; + } } 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 1bbd2e671e59a..9c123fcb330dd 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php @@ -45,7 +45,7 @@ protected function setUp() public function testGetOptions() { $options = $this->configOptionsList->getOptions(); - $this->assertCount(5, $options); + $this->assertCount(6, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -66,6 +66,10 @@ public function testGetOptions() $this->assertArrayHasKey(4, $options); $this->assertInstanceOf(TextConfigOption::class, $options[4]); $this->assertEquals('cache-backend-redis-password', $options[4]->getName()); + + $this->assertArrayHasKey(5, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[5]); + $this->assertEquals('cache-id-prefix', $options[5]->getName()); } /** @@ -85,7 +89,8 @@ public function testCreateConfigCacheRedis() 'port' => '', 'database' => '', 'password' => '' - ] + ], + 'id_prefix' => $this->expectedIdPrefix(), ] ] ] @@ -111,7 +116,8 @@ public function testCreateConfigWithRedisConfig() 'port' => '1234', 'database' => '5', 'password' => '' - ] + ], + 'id_prefix' => $this->expectedIdPrefix(), ] ] ] @@ -128,6 +134,54 @@ public function testCreateConfigWithRedisConfig() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testCreateConfigCacheRedis + */ + public function testCreateConfigWithFileCache() + { + $this->deploymentConfigMock->method('get')->willReturn(''); + + $expectedConfigData = [ + 'cache' => [ + 'frontend' => [ + 'default' => [ + 'id_prefix' => $this->expectedIdPrefix(), + ] + ] + ] + ]; + + $configData = $this->configOptionsList->createConfig([], $this->deploymentConfigMock); + + $this->assertEquals($expectedConfigData, $configData->getData()); + } + + /** + * testCreateConfigCacheRedis + */ + public function testCreateConfigWithIdPrefix() + { + $this->deploymentConfigMock->method('get')->willReturn(''); + + $explicitPrefix = 'XXX_'; + $expectedConfigData = [ + 'cache' => [ + 'frontend' => [ + 'default' => [ + 'id_prefix' => $explicitPrefix, + ] + ] + ] + ]; + + $configData = $this->configOptionsList->createConfig( + ['cache-id-prefix' => $explicitPrefix], + $this->deploymentConfigMock + ); + + $this->assertEquals($expectedConfigData, $configData->getData()); + } + /** * testValidateWithValidInput */ @@ -160,4 +214,14 @@ public function testValidateWithInvalidInput() $this->assertCount(1, $errors); $this->assertEquals("Invalid cache handler 'clay-tablet'", $errors[0]); } + + /** + * The default ID prefix, based on installation directory + * + * @return string + */ + private function expectedIdPrefix(): string + { + return substr(\md5(dirname(__DIR__, 8)), 0, 3) . '_'; + } } 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 6f8e97cd5dd8d..1cf3937f98684 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php @@ -45,7 +45,7 @@ protected function setUp() public function testGetOptions() { $options = $this->configList->getOptions(); - $this->assertCount(6, $options); + $this->assertCount(7, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -70,6 +70,10 @@ public function testGetOptions() $this->assertArrayHasKey(5, $options); $this->assertInstanceOf(TextConfigOption::class, $options[5]); $this->assertEquals('page-cache-redis-password', $options[5]->getName()); + + $this->assertArrayHasKey(6, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[6]); + $this->assertEquals('page-cache-id-prefix', $options[6]->getName()); } /** @@ -90,7 +94,8 @@ public function testCreateConfigWithRedis() 'database' => '', 'compress_data' => '', 'password' => '' - ] + ], + 'id_prefix' => $this->expectedIdPrefix(), ] ] ] @@ -117,7 +122,8 @@ public function testCreateConfigWithRedisConfiguration() 'database' => '6', 'compress_data' => '1', 'password' => '' - ] + ], + 'id_prefix' => $this->expectedIdPrefix(), ] ] ] @@ -136,6 +142,54 @@ public function testCreateConfigWithRedisConfiguration() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testCreateConfigWithRedis + */ + public function testCreateConfigWithFileCache() + { + $this->deploymentConfigMock->method('get')->willReturn(''); + + $expectedConfigData = [ + 'cache' => [ + 'frontend' => [ + 'page_cache' => [ + 'id_prefix' => $this->expectedIdPrefix(), + ] + ] + ] + ]; + + $configData = $this->configList->createConfig([], $this->deploymentConfigMock); + + $this->assertEquals($expectedConfigData, $configData->getData()); + } + + /** + * testCreateConfigCacheRedis + */ + public function testCreateConfigWithIdPrefix() + { + $this->deploymentConfigMock->method('get')->willReturn(''); + + $explicitPrefix = 'XXX_'; + $expectedConfigData = [ + 'cache' => [ + 'frontend' => [ + 'page_cache' => [ + 'id_prefix' => $explicitPrefix, + ] + ] + ] + ]; + + $configData = $this->configList->createConfig( + ['page-cache-id-prefix' => $explicitPrefix], + $this->deploymentConfigMock + ); + + $this->assertEquals($expectedConfigData, $configData->getData()); + } + /** * testValidationWithValidData */ @@ -169,4 +223,14 @@ public function testValidationWithInvalidData() $this->assertCount(1, $errors); $this->assertEquals('Invalid cache handler \'foobar\'', $errors[0]); } + + /** + * The default ID prefix, based on installation directory + * + * @return string + */ + private function expectedIdPrefix(): string + { + return substr(\md5(dirname(__DIR__, 8)), 0, 3) . '_'; + } }