diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index 39ed11b1806cd..853cc65270306 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -195,25 +195,6 @@ public function execute() ? $model->getAttributeCode() : $this->getRequest()->getParam('attribute_code'); $attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]); - if (strlen($attributeCode) > 0) { - $validatorAttrCode = new \Zend_Validate_Regex( - ['pattern' => '/^[a-zA-Z\x{600}-\x{6FF}][a-zA-Z\x{600}-\x{6FF}_0-9]{0,30}$/u'] - ); - if (!$validatorAttrCode->isValid($attributeCode)) { - $this->messageManager->addErrorMessage( - __( - 'Attribute code "%1" is invalid. Please use only letters (a-z or A-Z), ' . - 'numbers (0-9) or underscore(_) in this field, first character should be a letter.', - $attributeCode - ) - ); - return $this->returnResult( - 'catalog/*/edit', - ['attribute_id' => $attributeId, '_current' => true], - ['error' => true] - ); - } - } $data['attribute_code'] = $attributeCode; //validate frontend_input diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index 124ee1abb078e..c74a382724a00 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -7,12 +7,13 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -use Magento\Framework\Serialize\Serializer\FormData; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction; +use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction; +use Magento\Framework\Serialize\Serializer\FormData; /** * Product attribute validate controller. @@ -43,6 +44,11 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo */ private $formDataSerializer; + /** + * @var AttributeCodeValidator + */ + private $attributeCodeValidator; + /** * Constructor * @@ -54,6 +60,7 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @param array $multipleAttributeList * @param FormData|null $formDataSerializer + * @param AttributeCodeValidator|null $attributeCodeValidator */ public function __construct( \Magento\Backend\App\Action\Context $context, @@ -63,7 +70,8 @@ public function __construct( \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, array $multipleAttributeList = [], - FormData $formDataSerializer = null + FormData $formDataSerializer = null, + AttributeCodeValidator $attributeCodeValidator = null ) { parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory); $this->resultJsonFactory = $resultJsonFactory; @@ -71,6 +79,9 @@ public function __construct( $this->multipleAttributeList = $multipleAttributeList; $this->formDataSerializer = $formDataSerializer ?: ObjectManager::getInstance() ->get(FormData::class); + $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get( + AttributeCodeValidator::class + ); } /** @@ -115,6 +126,12 @@ public function execute() $response->setError(true); $response->setProductAttribute($attribute->toArray()); } + + if (!$this->attributeCodeValidator->isValid($attributeCode)) { + $this->setMessageToResponse($response, $this->attributeCodeValidator->getMessages()); + $response->setError(true); + } + if ($this->getRequest()->has('new_attribute_set_name')) { $setName = $this->getRequest()->getParam('new_attribute_set_name'); /** @var $attributeSet \Magento\Eav\Model\Entity\Attribute\Set */ diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php index ced65b2d2e15d..30d3503e4640e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php @@ -7,6 +7,7 @@ use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save; +use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator; use Magento\Framework\Serialize\Serializer\FormData; use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest; use Magento\Catalog\Model\Product\AttributeSet\BuildFactory; @@ -94,6 +95,11 @@ class SaveTest extends AttributeTest */ private $productAttributeMock; + /** + * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeCodeValidatorMock; + protected function setUp() { parent::setUp(); @@ -138,6 +144,9 @@ protected function setUp() $this->formDataSerializerMock = $this->getMockBuilder(FormData::class) ->disableOriginalConstructor() ->getMock(); + $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class) + ->disableOriginalConstructor() + ->getMock(); $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class) ->setMethods(['getId', 'get']) ->getMockForAbstractClass(); @@ -171,6 +180,7 @@ protected function getModel() 'groupCollectionFactory' => $this->groupCollectionFactoryMock, 'layoutFactory' => $this->layoutFactoryMock, 'formDataSerializer' => $this->formDataSerializerMock, + 'attributeCodeValidator' => $this->attributeCodeValidatorMock ]); } @@ -224,6 +234,10 @@ public function testExecute() $this->productAttributeMock ->method('getAttributeCode') ->willReturn('test_code'); + $this->attributeCodeValidatorMock + ->method('isValid') + ->with('test_code') + ->willReturn(true); $this->requestMock->expects($this->once()) ->method('getPostValue') ->willReturn($data); diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php index c6210f93e1290..742148b1bf7f1 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute; use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate; +use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator; use Magento\Framework\Serialize\Serializer\FormData; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest; @@ -67,6 +68,11 @@ class ValidateTest extends AttributeTest */ private $formDataSerializerMock; + /** + * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeCodeValidatorMock; + protected function setUp() { parent::setUp(); @@ -95,6 +101,9 @@ protected function setUp() $this->formDataSerializerMock = $this->getMockBuilder(FormData::class) ->disableOriginalConstructor() ->getMock(); + $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class) + ->disableOriginalConstructor() + ->getMock(); $this->contextMock->expects($this->any()) ->method('getObjectManager') @@ -117,6 +126,7 @@ protected function getModel() 'layoutFactory' => $this->layoutFactoryMock, 'multipleAttributeList' => ['select' => 'option'], 'formDataSerializer' => $this->formDataSerializerMock, + 'attributeCodeValidator' => $this->attributeCodeValidatorMock, ] ); } @@ -141,6 +151,12 @@ public function testExecute() $this->attributeMock->expects($this->once()) ->method('loadByCode') ->willReturnSelf(); + + $this->attributeCodeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('test_attribute_code') + ->willReturn(true); + $this->requestMock->expects($this->once()) ->method('has') ->with('new_attribute_set_name') @@ -190,6 +206,11 @@ public function testUniqueValidation(array $options, $isError) ->with($serializedOptions) ->willReturn($options); + $this->attributeCodeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('test_attribute_code') + ->willReturn(true); + $this->objectManagerMock->expects($this->once()) ->method('create') ->willReturn($this->attributeMock); @@ -333,6 +354,11 @@ public function testEmptyOption(array $options, $result) ->method('loadByCode') ->willReturnSelf(); + $this->attributeCodeValidatorMock->expects($this->once()) + ->method('isValid') + ->with('test_attribute_code') + ->willReturn(true); + $this->resultJsonFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->resultJson); @@ -444,6 +470,10 @@ public function testExecuteWithOptionsDataError() [\Magento\Eav\Model\Entity\Attribute\Set::class, [], $this->attributeSetMock] ]); + $this->attributeCodeValidatorMock + ->method('isValid') + ->willReturn(true); + $this->attributeMock ->method('loadByCode') ->willReturnSelf(); @@ -463,4 +493,81 @@ public function testExecuteWithOptionsDataError() $this->getModel()->execute(); } + + /** + * Test execute with an invalid attribute code + * + * @dataProvider provideInvalidAttributeCodes + * @param string $attributeCode + * @param $result + * @throws \Magento\Framework\Exception\NotFoundException + */ + public function testExecuteWithInvalidAttributeCode($attributeCode, $result) + { + $serializedOptions = '{"key":"value"}'; + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['frontend_label', null, null], + ['frontend_input', 'select', 'multipleselect'], + ['attribute_code', null, $attributeCode], + ['new_attribute_set_name', null, 'test_attribute_set_name'], + ['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'], + ['serialized_options', '[]', $serializedOptions], + ]); + + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with($serializedOptions) + ->willReturn(["key" => "value"]); + + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->willReturn($this->attributeMock); + + $this->attributeMock->expects($this->once()) + ->method('loadByCode') + ->willReturnSelf(); + + $this->attributeCodeValidatorMock->expects($this->once()) + ->method('isValid') + ->with($attributeCode) + ->willReturn(false); + + $this->attributeCodeValidatorMock->expects($this->once()) + ->method('getMessages') + ->willReturn(['Invalid Attribute Code.']); + + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->resultJson); + + $this->resultJson->expects($this->once()) + ->method('setJsonData') + ->willReturnArgument(0); + + $response = $this->getModel()->execute(); + $responseObject = json_decode($response); + + $this->assertEquals($responseObject, $result); + } + + /** + * Providing invalid attribute codes + * + * @return array + */ + public function provideInvalidAttributeCodes() + { + return [ + 'invalid attribute code' => [ + '.attribute_code', + (object) [ + 'error' => true, + 'message' => 'Invalid Attribute Code.', + ] + ] + ]; + } } diff --git a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php index a20c146d68d92..5dedf2c7e7eba 100644 --- a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php +++ b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php @@ -278,6 +278,7 @@ protected function getMultilineFieldConfig($attributeCode, array $attributeConfi for ($lineIndex = 0; $lineIndex < (int)$attributeConfig['size']; $lineIndex++) { $isFirstLine = $lineIndex === 0; $line = [ + 'label' => __("%1: Line %2", $attributeConfig['label'], $lineIndex + 1), 'component' => 'Magento_Ui/js/form/element/abstract', 'config' => [ // customScope is used to group elements within a single form e.g. they can be validated separately diff --git a/app/code/Magento/Config/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php index 2a29fa33feb74..8378c058c1955 100644 --- a/app/code/Magento/Config/Block/System/Config/Form.php +++ b/app/code/Magento/Config/Block/System/Config/Form.php @@ -424,6 +424,10 @@ private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Fie $backendModel = $field->getBackendModel(); // Backend models which implement ProcessorInterface are processed by ScopeConfigInterface if (!$backendModel instanceof ProcessorInterface) { + if (array_key_exists($path, $this->_configData)) { + $data = $this->_configData[$path]; + } + $backendModel->setPath($path) ->setValue($data) ->setWebsite($this->getWebsiteCode()) diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index 06a4abb985802..23054ad613c21 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -5,7 +5,9 @@ */ namespace Magento\Eav\Model\Entity; +use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator; use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; @@ -80,6 +82,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im */ protected $dateTimeFormatter; + /** + * @var AttributeCodeValidator|null + */ + private $attributeCodeValidator; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -100,6 +107,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param AttributeCodeValidator|null $attributeCodeValidator * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @codeCoverageIgnore */ @@ -122,7 +130,8 @@ public function __construct( DateTimeFormatterInterface $dateTimeFormatter, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + AttributeCodeValidator $attributeCodeValidator = null ) { parent::__construct( $context, @@ -145,6 +154,9 @@ public function __construct( $this->_localeResolver = $localeResolver; $this->reservedAttributeList = $reservedAttributeList; $this->dateTimeFormatter = $dateTimeFormatter; + $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get( + AttributeCodeValidator::class + ); } /** @@ -230,6 +242,13 @@ public function loadEntityAttributeIdBySet() */ public function beforeSave() { + if (isset($this->_data['attribute_code']) + && !$this->attributeCodeValidator->isValid($this->_data['attribute_code']) + ) { + $errorMessages = implode("\n", $this->attributeCodeValidator->getMessages()); + throw new LocalizedException(__($errorMessages)); + } + // prevent overriding product data if (isset($this->_data['attribute_code']) && $this->reservedAttributeList->isReservedAttribute($this)) { throw new LocalizedException( @@ -240,25 +259,6 @@ public function beforeSave() ); } - /** - * Check for maximum attribute_code length - */ - if (isset( - $this->_data['attribute_code'] - ) && !\Zend_Validate::is( - $this->_data['attribute_code'], - 'StringLength', - ['max' => self::ATTRIBUTE_CODE_MAX_LENGTH] - ) - ) { - throw new LocalizedException( - __( - 'The attribute code needs to be %1 characters or fewer. Re-enter the code and try again.', - self::ATTRIBUTE_CODE_MAX_LENGTH - ) - ); - } - $defaultValue = $this->getDefaultValue(); $hasDefaultValue = (string)$defaultValue != ''; @@ -513,7 +513,7 @@ public function __sleep() public function __wakeup() { parent::__wakeup(); - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $objectManager = ObjectManager::getInstance(); $this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $this->_localeResolver = $objectManager->get(\Magento\Framework\Locale\ResolverInterface::class); $this->reservedAttributeList = $objectManager->get(\Magento\Catalog\Model\Product\ReservedAttributeList::class); diff --git a/app/code/Magento/Eav/Model/Form.php b/app/code/Magento/Eav/Model/Form.php index c8c50521f5509..a34b53eede354 100644 --- a/app/code/Magento/Eav/Model/Form.php +++ b/app/code/Magento/Eav/Model/Form.php @@ -286,7 +286,8 @@ public function getFormCode() } /** - * Return entity type instance + * Return entity type instance. + * * Return EAV entity type if entity type is not defined * * @return \Magento\Eav\Model\Entity\Type @@ -323,6 +324,8 @@ public function getAttributes() if ($this->_attributes === null) { $this->_attributes = []; $this->_userAttributes = []; + $this->_systemAttributes = []; + $this->_allowedAttributes = []; /** @var $attribute \Magento\Eav\Model\Attribute */ foreach ($this->_getFilteredFormAttributeCollection() as $attribute) { $this->_attributes[$attribute->getAttributeCode()] = $attribute; diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Code.php b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php new file mode 100644 index 0000000000000..f3ee37721b8ce --- /dev/null +++ b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php @@ -0,0 +1,72 @@ + $minLength, 'max' => $maxLength] + ); + if (!$isAllowedLength) { + $errorMessages[] = __( + 'An attribute code must not be less than %1 and more than %2 characters.', + $minLength, + $maxLength + ); + } + + $this->_addMessages($errorMessages); + + return !$this->hasMessages(); + } +} diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php index 29f9163a6e91d..de285e81b1d03 100644 --- a/app/code/Magento/Eav/Setup/EavSetup.php +++ b/app/code/Magento/Eav/Setup/EavSetup.php @@ -9,7 +9,9 @@ use Magento\Eav\Model\Entity\Setup\Context; use Magento\Eav\Model\Entity\Setup\PropertyMapperInterface; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory; +use Magento\Eav\Model\Validator\Attribute\Code; use Magento\Framework\App\CacheInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Setup\ModuleDataSetupInterface; @@ -80,6 +82,11 @@ class EavSetup */ private $_defaultAttributeSetName = 'Default'; + /** + * @var Code + */ + private $attributeCodeValidator; + /** * Init * @@ -87,17 +94,22 @@ class EavSetup * @param Context $context * @param CacheInterface $cache * @param CollectionFactory $attrGroupCollectionFactory + * @param Code|null $attributeCodeValidator */ public function __construct( ModuleDataSetupInterface $setup, Context $context, CacheInterface $cache, - CollectionFactory $attrGroupCollectionFactory + CollectionFactory $attrGroupCollectionFactory, + Code $attributeCodeValidator = null ) { $this->cache = $cache; $this->attrGroupCollectionFactory = $attrGroupCollectionFactory; $this->attributeMapper = $context->getAttributeMapper(); $this->setup = $setup; + $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get( + Code::class + ); } /** @@ -777,38 +789,6 @@ private function _getValue($array, $key, $default = null) return isset($array[$key]) ? $array[$key] : $default; } - /** - * Validate attribute data before insert into table - * - * @param array $data - * @return true - * @throws LocalizedException - */ - private function _validateAttributeData($data) - { - $minLength = \Magento\Eav\Model\Entity\Attribute::ATTRIBUTE_CODE_MIN_LENGTH; - $maxLength = \Magento\Eav\Model\Entity\Attribute::ATTRIBUTE_CODE_MAX_LENGTH; - $attributeCode = isset($data['attribute_code']) ? $data['attribute_code'] : ''; - - $isAllowedLength = \Zend_Validate::is( - trim($attributeCode), - 'StringLength', - ['min' => $minLength, 'max' => $maxLength] - ); - - if (!$isAllowedLength) { - $errorMessage = __( - 'An attribute code must not be less than %1 and more than %2 characters.', - $minLength, - $maxLength - ); - - throw new LocalizedException($errorMessage); - } - - return true; - } - /** * Add attribute to an entity type * @@ -818,6 +798,8 @@ private function _validateAttributeData($data) * @param string $code * @param array $attr * @return $this + * @throws LocalizedException + * @throws \Zend_Validate_Exception */ public function addAttribute($entityTypeId, $code, array $attr) { @@ -828,7 +810,7 @@ public function addAttribute($entityTypeId, $code, array $attr) $this->attributeMapper->map($attr, $entityTypeId) ); - $this->_validateAttributeData($data); + $this->validateAttributeCode($data); $sortOrder = isset($attr['sort_order']) ? $attr['sort_order'] : null; $attributeId = $this->getAttribute($entityTypeId, $code, 'attribute_id'); @@ -1549,4 +1531,21 @@ private function _insertAttributeAdditionalData($entityTypeId, array $data) return $this; } + + /** + * Validate attribute code. + * + * @param array $data + * @throws LocalizedException + * @throws \Zend_Validate_Exception + */ + private function validateAttributeCode(array $data): void + { + $attributeCode = $data['attribute_code'] ?? ''; + if (!$this->attributeCodeValidator->isValid($attributeCode)) { + $errorMessage = implode('\n', $this->attributeCodeValidator->getMessages()); + + throw new LocalizedException(__($errorMessage)); + } + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php new file mode 100644 index 0000000000000..9db290bcba3e1 --- /dev/null +++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php @@ -0,0 +1,67 @@ +assertEquals($expected, $validator->isValid($attributeCode)); + } + + /** + * Data provider for testIsValid + * + * @return array + */ + public function isValidDataProvider(): array + { + return [ + [ + 'Attribute_code', + true + ], [ + 'attribute_1', + true + ],[ + 'Attribute_1', + true + ], [ + '_attribute_code', + false + ], [ + 'attribute.code', + false + ], [ + '1attribute_code', + false + ], [ + 'more_than_60_chars_more_than_60_chars_more_than_60_chars_more', + false + ] + ]; + } +} diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 6f9f81ba6b3fa..48954f1af90fc 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -202,6 +202,8 @@ + + diff --git a/app/code/Magento/Quote/i18n/en_US.csv b/app/code/Magento/Quote/i18n/en_US.csv index ae7453aa0d0cc..b24179297493a 100644 --- a/app/code/Magento/Quote/i18n/en_US.csv +++ b/app/code/Magento/Quote/i18n/en_US.csv @@ -65,3 +65,5 @@ error345,error345 Carts,Carts "Manage carts","Manage carts" "Invalid state change requested","Invalid state change requested" +"Validated Country Code","Validated Country Code" +"Validated Vat Number","Validated Vat Number" diff --git a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php index f9030ee75630b..76555ce8a6d8c 100644 --- a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php +++ b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php @@ -160,7 +160,8 @@ public function getConfigFlag($field) } /** - * Do request to shipment + * Do request to shipment. + * * Implementation must be in overridden method * * @param Request $request @@ -173,7 +174,8 @@ public function requestToShipment($request) } /** - * Do return of shipment + * Do return of shipment. + * * Implementation must be in overridden method * * @param Request $request @@ -275,6 +277,8 @@ public function getDeliveryConfirmationTypes(\Magento\Framework\DataObject $para } /** + * Validate request for available ship countries. + * * @param \Magento\Framework\DataObject $request * @return $this|bool|false|\Magento\Framework\Model\AbstractModel * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -400,6 +404,8 @@ public function getSortOrder() } /** + * Allows free shipping when all product items have free shipping. + * * @param \Magento\Quote\Model\Quote\Address\RateRequest $request * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -531,10 +537,10 @@ protected function _getPerorderPrice($cost, $handlingType, $handlingFee) } /** - * Sets the number of boxes for shipping + * Gets the average weight of each box available for shipping * - * @param int $weight in some measure - * @return int + * @param float $weight in some measure + * @return float */ public function getTotalNumOfBoxes($weight) { @@ -545,7 +551,7 @@ public function getTotalNumOfBoxes($weight) $maxPackageWeight = $this->getConfigData('max_package_weight'); if ($weight > $maxPackageWeight && $maxPackageWeight != 0) { $this->_numBoxes = ceil($weight / $maxPackageWeight); - $weight = $weight / $this->_numBoxes; + $weight = (float)$weight / $this->_numBoxes; } return $weight; @@ -671,7 +677,8 @@ protected function filterDebugData($data) } /** - * Recursive replace sensitive xml nodes values by specified mask + * Recursive replace sensitive xml nodes values by specified mask. + * * @param \SimpleXMLElement $xml * @return void */ diff --git a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php index 4d678f5c1cb94..1419fa375a117 100644 --- a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php +++ b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php @@ -5,12 +5,12 @@ */ namespace Magento\Sitemap\Model\ResourceModel\Catalog; +use Magento\Catalog\Helper\Product as HelperProduct; use Magento\Catalog\Model\Product\Image\UrlBuilder; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; -use Magento\Store\Model\Store; use Magento\Framework\App\ObjectManager; use Magento\Store\Model\ScopeInterface; -use Magento\Catalog\Helper\Product as HelperProduct; +use Magento\Store\Model\Store; /** * Sitemap resource product collection model @@ -259,7 +259,7 @@ protected function _joinAttribute($storeId, $attributeCode, $column = null) // Add attribute value to result set if needed if (isset($column)) { $this->_select->columns([ - $column => $columnValue + $column => $columnValue ]); } } @@ -282,7 +282,7 @@ protected function _getAttribute($attributeCode) 'attribute_id' => $attribute->getId(), 'table' => $attribute->getBackend()->getTable(), 'is_global' => $attribute->getIsGlobal() == - \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, + \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'backend_type' => $attribute->getBackendType(), ]; } @@ -324,7 +324,8 @@ public function getCollection($storeId) [] )->joinLeft( ['url_rewrite' => $this->getTable('url_rewrite')], - 'e.entity_id = url_rewrite.entity_id AND url_rewrite.is_autogenerated = 1 AND url_rewrite.metadata IS ' + 'e.entity_id = url_rewrite.entity_id AND url_rewrite.is_autogenerated = 1 ' + . 'AND NULLIF(url_rewrite.metadata,"") IS ' . $urlsConfigCondition . 'NULL' . $connection->quoteInto(' AND url_rewrite.store_id = ?', $store->getId()) . $connection->quoteInto(' AND url_rewrite.entity_type = ?', ProductUrlRewriteGenerator::ENTITY_TYPE), diff --git a/app/code/Magento/Ui/Component/Form.php b/app/code/Magento/Ui/Component/Form.php index 4033abba820e0..dc6e7b5ca06ab 100644 --- a/app/code/Magento/Ui/Component/Form.php +++ b/app/code/Magento/Ui/Component/Form.php @@ -10,6 +10,7 @@ use Magento\Framework\View\Element\UiComponentInterface; /** + * Ui component Form * @api * @since 100.0.2 */ @@ -53,14 +54,15 @@ public function getComponentName() } /** - * {@inheritdoc} + * @inheritdoc */ public function getDataSourceData() { $dataSource = []; $id = $this->getContext()->getRequestParam($this->getContext()->getDataProvider()->getRequestFieldName(), null); - $filter = $this->filterBuilder->setField($this->getContext()->getDataProvider()->getPrimaryFieldName()) + $idFieldName = $this->getContext()->getDataProvider()->getPrimaryFieldName(); + $filter = $this->filterBuilder->setField($idFieldName) ->setValue($id) ->create(); $this->getContext()->getDataProvider() @@ -74,7 +76,7 @@ public function getDataSourceData() ]; } elseif (isset($data['items'])) { foreach ($data['items'] as $item) { - if ($item[$item['id_field_name']] == $id) { + if ($item[$idFieldName] == $id) { $dataSource = ['data' => ['general' => $item]]; } } diff --git a/app/code/Magento/Ui/Test/Unit/Component/FormTest.php b/app/code/Magento/Ui/Test/Unit/Component/FormTest.php index 6951583291df9..6df69c7d0e48d 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/FormTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/FormTest.php @@ -9,7 +9,6 @@ use Magento\Framework\Api\FilterBuilder; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; -use Magento\Framework\View\Element\UiComponent\Processor; use Magento\Ui\Component\Form; class FormTest extends \PHPUnit\Framework\TestCase @@ -215,4 +214,65 @@ public function testGetDataSourceDataWithoutId() $this->assertEquals($dataSource, $this->model->getDataSourceData()); } + + public function testGetDataSourceDataWithAbstractDataProvider() + { + $requestFieldName = 'request_id'; + $primaryFieldName = 'primary_id'; + $fieldId = 44; + $row = ['key' => 'value', $primaryFieldName => $fieldId]; + $data = [ + 'items' => [$row], + ]; + $dataSource = [ + 'data' => [ + 'general' => $row + ], + ]; + + /** @var DataProviderInterface|\PHPUnit_Framework_MockObject_MockObject $dataProviderMock */ + $dataProviderMock = + $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface::class) + ->getMock(); + $dataProviderMock->expects($this->once()) + ->method('getRequestFieldName') + ->willReturn($requestFieldName); + $dataProviderMock->expects($this->once()) + ->method('getPrimaryFieldName') + ->willReturn($primaryFieldName); + + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProviderMock); + $this->contextMock->expects($this->once()) + ->method('getRequestParam') + ->with($requestFieldName) + ->willReturn($fieldId); + + /** @var Filter|\PHPUnit_Framework_MockObject_MockObject $filterMock */ + $filterMock = $this->getMockBuilder(\Magento\Framework\Api\Filter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->filterBuilderMock->expects($this->once()) + ->method('setField') + ->with($primaryFieldName) + ->willReturnSelf(); + $this->filterBuilderMock->expects($this->once()) + ->method('setValue') + ->with($fieldId) + ->willReturnSelf(); + $this->filterBuilderMock->expects($this->once()) + ->method('create') + ->willReturn($filterMock); + + $dataProviderMock->expects($this->once()) + ->method('addFilter') + ->with($filterMock); + $dataProviderMock->expects($this->once()) + ->method('getData') + ->willReturn($data); + + $this->assertEquals($dataSource, $this->model->getDataSourceData()); + } } diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv index 039e28f318176..1ce2692886ae3 100644 --- a/app/code/Magento/Ui/i18n/en_US.csv +++ b/app/code/Magento/Ui/i18n/en_US.csv @@ -78,6 +78,7 @@ Keyword,Keyword "Empty Value.","Empty Value." "Please use only letters (a-z or A-Z), numbers (0-9) or spaces only in this field.","Please use only letters (a-z or A-Z), numbers (0-9) or spaces only in this field." "Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter.","Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter." +"Attribute code ""%1"" is invalid. Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter.","Attribute code ""%1"" is invalid. Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, and the first character should be a letter." "Please use only letters (a-z or A-Z), numbers (0-9), spaces and ""#"" in this field.","Please use only letters (a-z or A-Z), numbers (0-9), spaces and ""#"" in this field." "Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.","Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890." "Please enter a valid fax number (Ex: 123-456-7890).","Please enter a valid fax number (Ex: 123-456-7890)." diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 8c60f5a53a2d9..9cb1fe615aa42 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -77,7 +77,7 @@ class Carrier extends AbstractCarrierOnline implements CarrierInterface * * @var string */ - protected $_defaultCgiGatewayUrl = 'http://www.ups.com:80/using/services/rave/qcostcgi.cgi'; + protected $_defaultCgiGatewayUrl = 'https://www.ups.com/using/services/rave/qcostcgi.cgi'; /** * Test urls for shipment diff --git a/app/code/Magento/Ups/etc/config.xml b/app/code/Magento/Ups/etc/config.xml index e2ac1c6d6c443..791b325c65e3f 100644 --- a/app/code/Magento/Ups/etc/config.xml +++ b/app/code/Magento/Ups/etc/config.xml @@ -19,7 +19,7 @@ RES GND - http://www.ups.com/using/services/rave/qcostcgi.cgi + https://www.ups.com/using/services/rave/qcostcgi.cgi https://onlinetools.ups.com/ups.app/xml/Rate 0 Magento\Ups\Model\Carrier diff --git a/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml b/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml index 7d8151d270308..de8575178d06d 100644 --- a/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml +++ b/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml @@ -14,6 +14,8 @@ urlrewriteGrid Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection url_rewrite_id + + 1 diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less index 54ba530092cc0..213b8131815b3 100644 --- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less @@ -165,7 +165,7 @@ // Checkout address (create shipping address) .field.street { - .field.additional { + .field { .label { &:extend(.abs-visually-hidden all); } diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index bd8ddde98c506..6adf4b5b2f86b 100755 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -200,7 +200,7 @@ // Checkout address (create shipping address) .field.street { - .field.additional { + .field { .label { &:extend(.abs-visually-hidden all); } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php index f0ee8c7b3c2bb..ddebbf37b16d1 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php @@ -158,14 +158,9 @@ private function isModuleAnnotation(string $fixture) */ private function getModulePath(string $fixture) { - $fixturePathParts = explode('::', $fixture, 2); - $moduleName = $fixturePathParts[0]; - $fixtureFile = $fixturePathParts[1]; + [$moduleName, $fixtureFile] = explode('::', $fixture, 2); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var ComponentRegistrar $componentRegistrar */ - $componentRegistrar = $objectManager->get(ComponentRegistrar::class); - $modulePath = $componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName); + $modulePath = (new ComponentRegistrar())->getPath(ComponentRegistrar::MODULE, $moduleName); if ($modulePath === null) { throw new \Magento\Framework\Exception\LocalizedException( @@ -173,7 +168,7 @@ private function getModulePath(string $fixture) ); } - return $modulePath . '/' . $fixtureFile; + return $modulePath . '/' . ltrim($fixtureFile, '/'); } /** diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php index db7f57362d807..ba23ecd13b6a7 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php @@ -4,13 +4,14 @@ * See COPYING.txt for license details. */ -/** - * Implementation of the @magentoDataFixture DocBlock annotation - */ namespace Magento\TestFramework\Annotation; +use Magento\Framework\Component\ComponentRegistrar; use PHPUnit\Framework\Exception; +/** + * Implementation of the @magentoDataFixtureBeforeTransaction DocBlock annotation + */ class DataFixtureBeforeTransaction { /** @@ -93,6 +94,8 @@ protected function _getFixtures(\PHPUnit\Framework\TestCase $test, $scope = null $fixtureMethod = [get_class($test), $fixture]; if (is_callable($fixtureMethod)) { $result[] = $fixtureMethod; + } elseif ($this->isModuleAnnotation($fixture)) { + $result[] = $this->getModulePath($fixture); } else { $result[] = $this->_fixtureBaseDir . '/' . $fixture; } @@ -102,6 +105,42 @@ protected function _getFixtures(\PHPUnit\Framework\TestCase $test, $scope = null } /** + * Check is the Annotation like Magento_InventoryApi::Test/_files/products.php + * + * @param string $fixture + * @return bool + */ + private function isModuleAnnotation(string $fixture) + { + return (strpos($fixture, '::') !== false); + } + + /** + * Resolve the Fixture + * + * @param string $fixture + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.StaticAccess) + */ + private function getModulePath(string $fixture) + { + [$moduleName, $fixtureFile] = explode('::', $fixture, 2); + + $modulePath = (new ComponentRegistrar())->getPath(ComponentRegistrar::MODULE, $moduleName); + + if ($modulePath === null) { + throw new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase('Can\'t find registered Module with name %1 .', [$moduleName]) + ); + } + + return $modulePath . '/' . ltrim($fixtureFile, '/'); + } + + /** + * Get annotations for test. + * * @param \PHPUnit\Framework\TestCase $test * @return array */ diff --git a/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/Annotation/DataFixtureTest.php b/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/Annotation/DataFixtureTest.php index 22240ad8e1fe9..00af4419e1142 100644 --- a/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/Annotation/DataFixtureTest.php +++ b/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/Annotation/DataFixtureTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Test\Annotation; +use Magento\Framework\Component\ComponentRegistrar; + /** * Test class for \Magento\TestFramework\Annotation\DataFixture. * @@ -178,4 +180,16 @@ public function testRollbackTransactionRevertFixtureFile() ); $this->_object->rollbackTransaction(); } + + /** + * @magentoDataFixture Foo_DataFixtureDummy::Test/Integration/foo.php + * @SuppressWarnings(PHPMD.StaticAccess) + */ + public function testModuleDataFixture() + { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Foo_DataFixtureDummy', __DIR__); + $this->_object->expects($this->once())->method('_applyOneFixture') + ->with(__DIR__ . '/Test/Integration/foo.php'); + $this->_object->startTransaction($this); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php index fe08ec01a9715..e1d3e960593a9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php @@ -157,7 +157,7 @@ public function testWrongAttributeCode() $message = $messages->getItemsByType('error')[0]; $this->assertEquals( 'Attribute code "_()&&&?" is invalid. Please use only letters (a-z or A-Z),' - . ' numbers (0-9) or underscore(_) in this field, first character should be a letter.', + . ' numbers (0-9) or underscore (_) in this field, and the first character should be a letter.', $message->getText() ); } diff --git a/dev/tests/integration/testsuite/Magento/Eav/Setup/EavSetupTest.php b/dev/tests/integration/testsuite/Magento/Eav/Setup/EavSetupTest.php index 5d7a72c65597d..a5843f20ad98a 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Setup/EavSetupTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Setup/EavSetupTest.php @@ -55,7 +55,7 @@ public function addAttributeDataProvider() { return [ ['eav_setup_test'], - ['_59_characters_59_characters_59_characters_59_characters_59'], + ['characters_59_characters_59_characters_59_characters_59_59_'], ]; } @@ -90,6 +90,33 @@ public function addAttributeThrowExceptionDataProvider() ]; } + /** + * Verify that add attribute throw exception if attribute_code is not valid. + * + * @param string|null $attributeCode + * + * @dataProvider addInvalidAttributeThrowExceptionDataProvider + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Please use only letters (a-z or A-Z), numbers (0-9) or underscore (_) in this field, + */ + public function testAddInvalidAttributeThrowException($attributeCode) + { + $attributeData = $this->getAttributeData(); + $this->eavSetup->addAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode, $attributeData); + } + /** + * Data provider for testAddInvalidAttributeThrowException(). + * + * @return array + */ + public function addInvalidAttributeThrowExceptionDataProvider() + { + return [ + ['1first_character_is_not_letter'], + ['attribute.with.dots'], + ]; + } + /** * Get simple attribute data. */ diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php index 173064b472217..89a37429c47c9 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php @@ -27,6 +27,8 @@ 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_BACKEND_REDIS_COMPRESS_DATA = 'cache-backend-redis-compress-data'; + const INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB = 'cache-backend-redis-compression-lib'; const INPUT_KEY_CACHE_ID_PREFIX = 'cache-id-prefix'; const CONFIG_PATH_CACHE_BACKEND = 'cache/frontend/default/backend'; @@ -34,6 +36,8 @@ class Cache implements ConfigOptionsListInterface 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_BACKEND_COMPRESS_DATA = 'cache/frontend/default/backend_options/compress_data'; + const CONFIG_PATH_CACHE_BACKEND_COMPRESSION_LIB = 'cache/frontend/default/backend_options/compression_lib'; const CONFIG_PATH_CACHE_ID_PREFIX = 'cache/frontend/default/id_prefix'; /** @@ -43,7 +47,9 @@ class Cache implements ConfigOptionsListInterface self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => '0', self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => '6379', - self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => '' + self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => '', + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA => '1', + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB => '', ]; /** @@ -60,7 +66,9 @@ class Cache implements ConfigOptionsListInterface self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_CACHE_BACKEND_SERVER, self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_CACHE_BACKEND_DATABASE, self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_CACHE_BACKEND_PORT, - self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_CACHE_BACKEND_PASSWORD + self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_CACHE_BACKEND_COMPRESS_DATA, + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB => self::CONFIG_PATH_CACHE_BACKEND_COMPRESSION_LIB, ]; /** @@ -115,12 +123,24 @@ public function getOptions() self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, 'Redis server password' ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESS_DATA, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_COMPRESS_DATA, + 'Set to 0 to disable compression (default is 1, enabled)' + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_COMPRESSION_LIB, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_COMPRESSION_LIB, + 'Compression lib to use [snappy,lzf,l4z,zstd,gzip] (leave blank to determine automatically)' + ), new TextConfigOption( self::INPUT_KEY_CACHE_ID_PREFIX, TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_CACHE_ID_PREFIX, 'ID prefix for cache keys' - ) + ), ]; } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php index 7451b59356828..65bfc650c0206 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php @@ -6,10 +6,10 @@ namespace Magento\Setup\Model\ConfigOptionsList; -use Magento\Framework\Setup\ConfigOptionsListInterface; -use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\Data\ConfigData; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Setup\ConfigOptionsListInterface; use Magento\Framework\Setup\Option\SelectConfigOption; use Magento\Framework\Setup\Option\TextConfigOption; use Magento\Setup\Validator\RedisConnectionValidator; @@ -26,16 +26,18 @@ class PageCache implements ConfigOptionsListInterface const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE = 'page-cache-redis-db'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT = 'page-cache-redis-port'; - const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'page-cache-redis-compress-data'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD = 'page-cache-redis-password'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'page-cache-redis-compress-data'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESSION_LIB = 'page-cache-redis-compression-lib'; 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'; const CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE = 'cache/frontend/page_cache/backend_options/database'; const CONFIG_PATH_PAGE_CACHE_BACKEND_PORT = 'cache/frontend/page_cache/backend_options/port'; - const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/page_cache/backend_options/compress_data'; const CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD = 'cache/frontend/page_cache/backend_options/password'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/page_cache/backend_options/compress_data'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESSION_LIB = 'cache/frontend/page_cache/backend_options/compression_lib'; const CONFIG_PATH_PAGE_CACHE_ID_PREFIX = 'cache/frontend/page_cache/id_prefix'; /** @@ -45,8 +47,9 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => '1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => '6379', + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => '', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => '0', - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => '' + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESSION_LIB => '', ]; /** @@ -63,8 +66,10 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PORT, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA, - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESSION_LIB => + self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESSION_LIB, ]; /** @@ -113,6 +118,12 @@ public function getOptions() self::CONFIG_PATH_PAGE_CACHE_BACKEND_PORT, 'Redis server listen port' ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, + 'Redis server password' + ), new TextConfigOption( self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA, TextConfigOption::FRONTEND_WIZARD_TEXT, @@ -120,17 +131,17 @@ public function getOptions() 'Set to 1 to compress the full page cache (use 0 to disable)' ), new TextConfigOption( - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESSION_LIB, TextConfigOption::FRONTEND_WIZARD_TEXT, - self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, - 'Redis server password' + self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESSION_LIB, + 'Compression library to use [snappy,lzf,l4z,zstd,gzip] (leave blank to determine automatically)' ), 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' - ) + ), ]; } @@ -224,7 +235,7 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen self::CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE, $this->getDefaultConfigValue(self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE) ); - + $config['password'] = isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD]) ? $options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD] : $deploymentConfig->get( 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 9c123fcb330dd..783c11e941eed 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(6, $options); + $this->assertCount(8, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -69,7 +69,15 @@ public function testGetOptions() $this->assertArrayHasKey(5, $options); $this->assertInstanceOf(TextConfigOption::class, $options[5]); - $this->assertEquals('cache-id-prefix', $options[5]->getName()); + $this->assertEquals('cache-backend-redis-compress-data', $options[5]->getName()); + + $this->assertArrayHasKey(6, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[6]); + $this->assertEquals('cache-backend-redis-compression-lib', $options[6]->getName()); + + $this->assertArrayHasKey(7, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[7]); + $this->assertEquals('cache-id-prefix', $options[7]->getName()); } /** @@ -88,7 +96,9 @@ public function testCreateConfigCacheRedis() 'server' => '', 'port' => '', 'database' => '', - 'password' => '' + 'password' => '', + 'compress_data' => '', + 'compression_lib' => '', ], 'id_prefix' => $this->expectedIdPrefix(), ] @@ -115,18 +125,23 @@ public function testCreateConfigWithRedisConfig() 'server' => 'localhost', 'port' => '1234', 'database' => '5', - 'password' => '' + 'password' => '', + 'compress_data' => '1', + 'compression_lib' => 'gzip', ], 'id_prefix' => $this->expectedIdPrefix(), ] ] ] ]; + $options = [ 'cache-backend' => 'redis', 'cache-backend-redis-server' => 'localhost', 'cache-backend-redis-port' => '1234', - 'cache-backend-redis-db' => '5' + 'cache-backend-redis-db' => '5', + 'cache-backend-redis-compress-data' => '1', + 'cache-backend-redis-compression-lib' => 'gzip' ]; $configData = $this->configOptionsList->createConfig($options, $this->deploymentConfigMock); 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 1cf3937f98684..673168fe2fffd 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(7, $options); + $this->assertCount(8, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -65,15 +65,19 @@ public function testGetOptions() $this->assertArrayHasKey(4, $options); $this->assertInstanceOf(TextConfigOption::class, $options[4]); - $this->assertEquals('page-cache-redis-compress-data', $options[4]->getName()); + $this->assertEquals('page-cache-redis-password', $options[4]->getName()); $this->assertArrayHasKey(5, $options); $this->assertInstanceOf(TextConfigOption::class, $options[5]); - $this->assertEquals('page-cache-redis-password', $options[5]->getName()); + $this->assertEquals('page-cache-redis-compress-data', $options[5]->getName()); $this->assertArrayHasKey(6, $options); $this->assertInstanceOf(TextConfigOption::class, $options[6]); - $this->assertEquals('page-cache-id-prefix', $options[6]->getName()); + $this->assertEquals('page-cache-redis-compression-lib', $options[6]->getName()); + + $this->assertArrayHasKey(7, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[7]); + $this->assertEquals('page-cache-id-prefix', $options[7]->getName()); } /** @@ -93,7 +97,8 @@ public function testCreateConfigWithRedis() 'port' => '', 'database' => '', 'compress_data' => '', - 'password' => '' + 'password' => '', + 'compression_lib' => '', ], 'id_prefix' => $this->expectedIdPrefix(), ] @@ -120,8 +125,9 @@ public function testCreateConfigWithRedisConfiguration() 'server' => 'foo.bar', 'port' => '9000', 'database' => '6', + 'password' => '', 'compress_data' => '1', - 'password' => '' + 'compression_lib' => 'gzip', ], 'id_prefix' => $this->expectedIdPrefix(), ] @@ -134,7 +140,8 @@ public function testCreateConfigWithRedisConfiguration() 'page-cache-redis-server' => 'foo.bar', 'page-cache-redis-port' => '9000', 'page-cache-redis-db' => '6', - 'page-cache-redis-compress-data' => '1' + 'page-cache-redis-compress-data' => '1', + 'page-cache-redis-compression-lib' => 'gzip', ]; $configData = $this->configList->createConfig($options, $this->deploymentConfigMock);