Skip to content

Commit 1f72571

Browse files
committed
Merge branch '2.4-develop' into MC-36956
2 parents 6d05df0 + 4539cb0 commit 1f72571

File tree

45 files changed

+1650
-88
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1650
-88
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Catalog\Model\Attribute\Backend;
10+
11+
use Magento\Catalog\Model\AbstractModel;
12+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
13+
use Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend as ParentBackend;
14+
use Magento\Eav\Model\Entity\Attribute\Exception;
15+
use Magento\Framework\DataObject;
16+
use Magento\Framework\Exception\LocalizedException;
17+
use Magento\Framework\Validation\ValidationException;
18+
use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface;
19+
20+
/**
21+
* Default backend model for catalog attributes.
22+
*/
23+
class DefaultBackend extends ParentBackend
24+
{
25+
/**
26+
* @var WYSIWYGValidatorInterface
27+
*/
28+
private $wysiwygValidator;
29+
30+
/**
31+
* @param WYSIWYGValidatorInterface $wysiwygValidator
32+
*/
33+
public function __construct(WYSIWYGValidatorInterface $wysiwygValidator)
34+
{
35+
$this->wysiwygValidator = $wysiwygValidator;
36+
}
37+
38+
/**
39+
* Validate user HTML value.
40+
*
41+
* @param DataObject $object
42+
* @return void
43+
* @throws LocalizedException
44+
*/
45+
private function validateHtml(DataObject $object): void
46+
{
47+
$attribute = $this->getAttribute();
48+
$code = $attribute->getAttributeCode();
49+
if ($attribute instanceof Attribute && $attribute->getIsHtmlAllowedOnFront()) {
50+
$value = $object->getData($code);
51+
if ($value
52+
&& is_string($value)
53+
&& (!($object instanceof AbstractModel) || $object->getData($code) !== $object->getOrigData($code))
54+
) {
55+
try {
56+
$this->wysiwygValidator->validate($object->getData($code));
57+
} catch (ValidationException $exception) {
58+
$attributeException = new Exception(
59+
__(
60+
'Using restricted HTML elements for "%1". %2',
61+
$attribute->getName(),
62+
$exception->getMessage()
63+
),
64+
$exception
65+
);
66+
$attributeException->setAttributeCode($code)->setPart('backend');
67+
throw $attributeException;
68+
}
69+
}
70+
}
71+
}
72+
73+
/**
74+
* @inheritDoc
75+
*/
76+
public function beforeSave($object)
77+
{
78+
parent::beforeSave($object);
79+
$this->validateHtml($object);
80+
81+
return $this;
82+
}
83+
84+
/**
85+
* @inheritDoc
86+
*/
87+
public function validate($object)
88+
{
89+
$isValid = parent::validate($object);
90+
if ($isValid) {
91+
$this->validateHtml($object);
92+
}
93+
94+
return $isValid;
95+
}
96+
}

app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
namespace Magento\Catalog\Model\ResourceModel\Eav;
88

9+
use Magento\Catalog\Model\Attribute\Backend\DefaultBackend;
910
use Magento\Catalog\Model\Attribute\LockValidatorInterface;
11+
use Magento\Eav\Model\Entity;
1012
use Magento\Framework\Api\AttributeValueFactory;
1113
use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface;
1214

@@ -902,4 +904,17 @@ public function setIsFilterableInGrid($isFilterableInGrid)
902904
$this->setData(self::IS_FILTERABLE_IN_GRID, $isFilterableInGrid);
903905
return $this;
904906
}
907+
908+
/**
909+
* @inheritDoc
910+
*/
911+
protected function _getDefaultBackendModel()
912+
{
913+
$backend = parent::_getDefaultBackendModel();
914+
if ($backend === Entity::DEFAULT_BACKEND_MODEL) {
915+
$backend = DefaultBackend::class;
916+
}
917+
918+
return $backend;
919+
}
905920
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Catalog\Test\Unit\Model\Attribute\Backend;
10+
11+
use Magento\Catalog\Model\AbstractModel;
12+
use Magento\Catalog\Model\Attribute\Backend\DefaultBackend;
13+
use Magento\Framework\DataObject;
14+
use Magento\Framework\Validation\ValidationException;
15+
use Magento\Framework\Validator\HTML\WYSIWYGValidatorInterface;
16+
use PHPUnit\Framework\TestCase;
17+
use Magento\Eav\Model\Entity\Attribute as BasicAttribute;
18+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
19+
use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException;
20+
21+
class DefaultBackendTest extends TestCase
22+
{
23+
/**
24+
* Different cases for attribute validation.
25+
*
26+
* @return array
27+
*/
28+
public function getAttributeConfigurations(): array
29+
{
30+
return [
31+
'basic-attribute' => [true, false, true, 'basic', 'value', false, true, false],
32+
'non-html-attribute' => [false, false, false, 'non-html', 'value', false, false, false],
33+
'empty-html-attribute' => [false, false, true, 'html', null, false, true, false],
34+
'invalid-html-attribute' => [false, false, false, 'html', 'value', false, true, true],
35+
'valid-html-attribute' => [false, true, false, 'html', 'value', false, true, false],
36+
'changed-invalid-html-attribute' => [false, false, true, 'html', 'value', true, true, true],
37+
'changed-valid-html-attribute' => [false, true, true, 'html', 'value', true, true, false]
38+
];
39+
}
40+
41+
/**
42+
* Test attribute validation.
43+
*
44+
* @param bool $isBasic
45+
* @param bool $isValidated
46+
* @param bool $isCatalogEntity
47+
* @param string $code
48+
* @param mixed $value
49+
* @param bool $isChanged
50+
* @param bool $isHtmlAttribute
51+
* @param bool $exceptionThrown
52+
* @dataProvider getAttributeConfigurations
53+
*/
54+
public function testValidate(
55+
bool $isBasic,
56+
bool $isValidated,
57+
bool $isCatalogEntity,
58+
string $code,
59+
$value,
60+
bool $isChanged,
61+
bool $isHtmlAttribute,
62+
bool $exceptionThrown
63+
): void {
64+
if ($isBasic) {
65+
$attributeMock = $this->createMock(BasicAttribute::class);
66+
} else {
67+
$attributeMock = $this->createMock(Attribute::class);
68+
$attributeMock->expects($this->any())
69+
->method('getIsHtmlAllowedOnFront')
70+
->willReturn($isHtmlAttribute);
71+
}
72+
$attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($code);
73+
74+
$validatorMock = $this->getMockForAbstractClass(WYSIWYGValidatorInterface::class);
75+
if (!$isValidated) {
76+
$validatorMock->expects($this->any())
77+
->method('validate')
78+
->willThrowException(new ValidationException(__('HTML is invalid')));
79+
} else {
80+
$validatorMock->expects($this->any())->method('validate');
81+
}
82+
83+
if ($isCatalogEntity) {
84+
$objectMock = $this->createMock(AbstractModel::class);
85+
$objectMock->expects($this->any())
86+
->method('getOrigData')
87+
->willReturn($isChanged ? $value .'-OLD' : $value);
88+
} else {
89+
$objectMock = $this->createMock(DataObject::class);
90+
}
91+
$objectMock->expects($this->any())->method('getData')->with($code)->willReturn($value);
92+
93+
$model = new DefaultBackend($validatorMock);
94+
$model->setAttribute($attributeMock);
95+
96+
$actuallyThrownForSave = false;
97+
try {
98+
$model->beforeSave($objectMock);
99+
} catch (AttributeException $exception) {
100+
$actuallyThrownForSave = true;
101+
}
102+
$actuallyThrownForValidate = false;
103+
try {
104+
$model->validate($objectMock);
105+
} catch (AttributeException $exception) {
106+
$actuallyThrownForValidate = true;
107+
}
108+
$this->assertEquals($actuallyThrownForSave, $actuallyThrownForValidate);
109+
$this->assertEquals($actuallyThrownForSave, $exceptionThrown);
110+
}
111+
}

app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected function setUp(): void
6262
$this->closureMock = function () use ($productMock) {
6363
return $productMock;
6464
};
65-
$this->rollbackClosureMock = function () use ($productMock) {
65+
$this->rollbackClosureMock = function () {
6666
throw new \Exception(self::ERROR_MSG);
6767
};
6868

app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function resolve(
8787
$this->tiers->addProductFilter($productId);
8888

8989
return $this->valueFactory->create(
90-
function () use ($productId, $context) {
90+
function () use ($productId) {
9191
$tierPrices = $this->tiers->getProductTierPrices($productId);
9292

9393
return $tierPrices ?? [];

app/code/Magento/CatalogSearch/Model/Advanced.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@ protected function getPreparedSearchCriteria($attribute, $value)
359359
if (is_array($value)) {
360360
if (isset($value['from']) && isset($value['to'])) {
361361
if (!empty($value['from']) || !empty($value['to'])) {
362+
$from = '';
363+
$to = '';
362364
if (isset($value['currency'])) {
363365
/** @var $currencyModel Currency */
364366
$currencyModel = $this->_currencyFactory->create()->load($value['currency']);

app/code/Magento/Checkout/Test/Mftf/ActionGroup/LoggedInUserCheckoutFillingShippingSectionActionGroup.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
<selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{customerAddressVar.state}}" stepKey="selectRegion"/>
2525
<fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/>
2626
<fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/>
27-
<waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/>
27+
<waitForPageLoad stepKey="waitForLoadingMask"/>
2828
<click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/>
29-
<waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/>
29+
<waitForElementVisible selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/>
30+
<waitForPageLoad stepKey="waitForShippingLoadingMask"/>
3031
<click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/>
31-
<waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/>
32+
<waitForElementVisible selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/>
3233
<seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/>
3334
</actionGroup>
3435
</actionGroups>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Cms\Command;
10+
11+
use Magento\Cms\Model\Wysiwyg\Validator;
12+
use Symfony\Component\Console\Command\Command;
13+
use Symfony\Component\Console\Input\InputArgument;
14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
use Magento\Framework\App\Config\ConfigResource\ConfigInterface as ConfigWriter;
17+
use Magento\Framework\App\Cache\TypeListInterface as Cache;
18+
19+
/**
20+
* Command to toggle WYSIWYG content validation on/off.
21+
*/
22+
class WysiwygRestrictCommand extends Command
23+
{
24+
/**
25+
* @var ConfigWriter
26+
*/
27+
private $configWriter;
28+
29+
/**
30+
* @var Cache
31+
*/
32+
private $cache;
33+
34+
/**
35+
* @param ConfigWriter $configWriter
36+
* @param Cache $cache
37+
*/
38+
public function __construct(ConfigWriter $configWriter, Cache $cache)
39+
{
40+
parent::__construct();
41+
42+
$this->configWriter = $configWriter;
43+
$this->cache = $cache;
44+
}
45+
46+
/**
47+
* @inheritDoc
48+
*/
49+
protected function configure()
50+
{
51+
$this->setName('cms:wysiwyg:restrict');
52+
$this->setDescription('Set whether to enforce user HTML content validation or show a warning instead');
53+
$this->setDefinition([new InputArgument('restrict', InputArgument::REQUIRED, 'y\n')]);
54+
55+
parent::configure();
56+
}
57+
58+
/**
59+
* @inheritDoc
60+
*/
61+
protected function execute(InputInterface $input, OutputInterface $output)
62+
{
63+
$restrictArg = mb_strtolower((string)$input->getArgument('restrict'));
64+
$restrict = $restrictArg === 'y' ? '1' : '0';
65+
$this->configWriter->saveConfig(Validator::CONFIG_PATH_THROW_EXCEPTION, $restrict);
66+
$this->cache->cleanType('config');
67+
68+
$output->writeln('HTML user content validation is now ' .($restrictArg === 'y' ? 'enforced' : 'suggested'));
69+
70+
return 0;
71+
}
72+
}

0 commit comments

Comments
 (0)