| Current Path : /var/www/html/vendor/magento/framework/Validator/HTML/ |
| Current File : /var/www/html/vendor/magento/framework/Validator/HTML/ConfigurableWYSIWYGValidator.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Framework\Validator\HTML;
use Magento\Framework\Validation\ValidationException;
/**
* Validates user HTML based on configuration.
*/
class ConfigurableWYSIWYGValidator implements WYSIWYGValidatorInterface
{
/**
* @var string[]
*/
private $allowedTags;
/**
* @var string[]
*/
private $allowedAttributes;
/**
* @var string[]
*/
private $attributesAllowedByTags;
/**
* @var AttributeValidatorInterface[][]
*/
private $attributeValidators;
/**
* @var TagValidatorInterface[][]
*/
private $tagValidators;
/**
* @param string[] $allowedTags
* @param string[] $allowedAttributes
* @param string[][] $attributesAllowedByTags
* @param AttributeValidatorInterface[][] $attributeValidators
* @param TagValidatorInterface[][] $tagValidators
*/
public function __construct(
array $allowedTags,
array $allowedAttributes = [],
array $attributesAllowedByTags = [],
array $attributeValidators = [],
array $tagValidators = []
) {
if (empty(array_filter($allowedTags))) {
throw new \InvalidArgumentException('List of allowed HTML tags cannot be empty');
}
$this->allowedTags = array_unique($allowedTags);
$this->allowedAttributes = array_unique($allowedAttributes);
$this->attributesAllowedByTags = array_filter(
$attributesAllowedByTags,
function (string $tag) use ($allowedTags): bool {
return in_array($tag, $allowedTags, true);
},
ARRAY_FILTER_USE_KEY
);
$this->attributeValidators = $attributeValidators;
$this->tagValidators = $tagValidators;
}
/**
* @inheritDoc
*/
public function validate(string $content): void
{
if (mb_strlen($content) === 0) {
return;
}
$dom = $this->loadHtml($content);
$xpath = new \DOMXPath($dom);
$this->validateConfigured($xpath);
$this->callAttributeValidators($xpath);
$this->callTagValidators($xpath);
}
/**
* Check declarative restrictions
*
* @param \DOMXPath $xpath
* @return void
* @throws ValidationException
*/
private function validateConfigured(\DOMXPath $xpath): void
{
//Validating tags
$found = $xpath->query(
'//*['
. implode(
' and ',
array_map(
function (string $tag): string {
return "name() != '$tag'";
},
array_merge($this->allowedTags, ['body', 'html'])
)
)
.']'
);
if (count($found)) {
throw new ValidationException(
__('Allowed HTML tags are: %1', implode(', ', $this->allowedTags))
);
}
//Validating attributes
if ($this->attributesAllowedByTags) {
foreach ($this->allowedTags as $tag) {
$allowed = [$this->allowedAttributes];
if (!empty($this->attributesAllowedByTags[$tag])) {
$allowed[] = $this->attributesAllowedByTags[$tag];
}
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
$allowed = array_unique(array_merge(...$allowed));
$allowedQuery = '';
if ($allowed) {
$allowedQuery = '['
. implode(
' and ',
array_map(
function (string $attribute): string {
return "name() != '$attribute'";
},
$allowed
)
)
.']';
}
$found = $xpath->query("//$tag/@*$allowedQuery");
if (count($found)) {
throw new ValidationException(
__('Allowed HTML attributes for tag "%1" are: %2', $tag, implode(',', $allowed))
);
}
}
} else {
$allowed = '';
if ($this->allowedAttributes) {
$allowed = '['
. implode(
' and ',
array_map(
function (string $attribute): string {
return "name() != '$attribute'";
},
$this->allowedAttributes
)
)
.']';
}
$found = $xpath->query("//@*$allowed");
if (count($found)) {
throw new ValidationException(
__('Allowed HTML attributes are: %1', implode(',', $this->allowedAttributes))
);
}
}
}
/**
* Validate allowed HTML attributes' content.
*
* @param \DOMXPath $xpath
* @throws ValidationException
* @return void
*/
private function callAttributeValidators(\DOMXPath $xpath): void
{
if ($this->attributeValidators) {
foreach ($this->attributeValidators as $attr => $validators) {
$found = $xpath->query("//@*[name() = '$attr']");
foreach ($found as $attribute) {
foreach ($validators as $validator) {
$validator->validate($attribute->parentNode->tagName, $attribute->name, $attribute->value);
}
}
}
}
}
/**
* Validate allowed tags.
*
* @param \DOMXPath $xpath
* @return void
* @throws ValidationException
*/
private function callTagValidators(\DOMXPath $xpath): void
{
if ($this->tagValidators) {
foreach ($this->tagValidators as $tag => $validators) {
$found = $xpath->query("//*[name() = '$tag']");
/** @var \DOMElement $tagNode */
foreach ($found as $tagNode) {
$attributes = [];
if ($tagNode->hasAttributes()) {
/** @var \DOMAttr $attributeNode */
foreach ($tagNode->attributes as $attributeNode) {
$attributes[$attributeNode->name] = $attributeNode->value;
}
}
foreach ($validators as $validator) {
$validator->validate($tagNode->tagName, $attributes, $tagNode->textContent, $this);
}
}
}
}
}
/**
* Load DOM.
*
* @param string $content
* @return \DOMDocument
* @throws ValidationException
*/
private function loadHtml(string $content): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
set_error_handler(
function () use (&$loaded) {
$loaded = false;
}
);
$loaded = $dom->loadHTML("<html><body>$content</body></html>");
restore_error_handler();
if (!$loaded) {
throw new ValidationException(__('Invalid HTML content provided'));
}
return $dom;
}
}