View source
<?php
namespace Drupal\markdown\Util;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\filter\Plugin\Filter\FilterHtml as CoreFilterHtml;
use Drupal\markdown\Plugin\Markdown\ParserInterface;
use Drupal\markdown\PluginManager\AllowedHtmlManager;
use Drupal\markdown\Traits\ParserAwareTrait;
class FilterHtml extends CoreFilterHtml implements ParserAwareInterface {
use ParserAwareTrait;
const ASTERISK_PLACEHOLDER = '__zqh6vxfbk3cg__';
public static function create($allowedHtml = '') {
return new static([
'settings' => [
'allowed_html' => $allowedHtml,
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
],
], 'filter_html', [
'provider' => 'markdown',
]);
}
public static function fromParser(ParserInterface $parser) {
return static::create($parser
->getCustomAllowedHtml())
->setParser($parser);
}
public static function mergeAllowedTags(array $normalizedTags, array $tags) {
$args = func_get_args();
$normalizedTags = array_shift($args);
foreach ($args as $tags) {
if (!is_array($tags) || !$tags) {
continue;
}
$tags = static::normalizeTags($tags);
foreach ($tags as $tag => $attributes) {
if (!isset($normalizedTags[$tag])) {
$normalizedTags[$tag] = $attributes;
continue;
}
if (!empty($normalizedTags[$tag]['*'])) {
continue;
}
if (!empty($attributes['*'])) {
$normalizedTags[$tag] = [
'*' => TRUE,
];
continue;
}
foreach ($attributes as $name => $value) {
if (!isset($normalizedTags[$tag][$name])) {
$normalizedTags[$tag][$name] = $value;
continue;
}
if ($normalizedTags[$tag][$name] === TRUE) {
continue;
}
if ($value === TRUE) {
$normalizedTags[$tag][$name] = $value;
continue;
}
if (is_array($value)) {
if (!is_array($normalizedTags[$tag][$name])) {
$normalizedTags[$tag][$name] = [];
}
$normalizedTags[$tag][$name] = array_replace($normalizedTags[$tag][$name], $value);
}
}
}
}
ksort($normalizedTags);
return $normalizedTags;
}
public static function normalizeTags(array $tags) {
$tags = array_map(function ($attributes) {
if (is_array($attributes)) {
foreach ($attributes as $name => $value) {
if (!is_bool($value)) {
$attributes[$name] = is_array($value) ? $value : [
$value => TRUE,
];
}
}
return $attributes;
}
return $attributes === FALSE ? [] : [
'*' => TRUE,
];
}, $tags);
ksort($tags);
return $tags;
}
protected static function extractDomNodeTags(\DOMNode $node, $attributeNames = TRUE, $attributeValues = FALSE) {
$tags = [];
if (!isset($tags[$node->nodeName])) {
$tags[$node->nodeName] = [];
}
if ($attributeNames && $node->attributes) {
for ($i = 0, $l = $node->attributes->length; $i < $l; ++$i) {
$attribute = $node->attributes
->item($i);
$name = $attribute->name;
$tags[$node->nodeName][$name] = $attributeValues ? $attribute->nodeValue : TRUE;
}
if ($node
->hasChildNodes()) {
foreach ($node->childNodes as $childNode) {
$tags = NestedArray::mergeDeep($tags, static::extractDomNodeTags($childNode));
}
}
}
return $tags;
}
public static function tagsFromHtml($html = NULL, $attributeNames = TRUE, $attributeValues = FALSE) {
$tags = [];
if (!$html || strpos($html, '<') === FALSE) {
return $tags;
}
libxml_use_internal_errors(true);
$dom = new \DOMDocument();
$dom
->loadHTML($html);
libxml_clear_errors();
foreach ($dom
->getElementsByTagName('body')
->item(0)->childNodes as $childNode) {
$tags = NestedArray::mergeDeep($tags, static::extractDomNodeTags($childNode, $attributeNames, $attributeValues));
}
return $tags;
}
public static function tagsToString(array $tags = []) {
$items = [];
ksort($tags);
foreach (static::normalizeTags($tags) as $tag => $attributes) {
$tag = "<{$tag}";
if (is_array($attributes)) {
foreach ($attributes as $attribute => $value) {
if (!$value) {
continue;
}
$tag .= " {$attribute}";
if ($value && $value !== TRUE) {
if (is_array($value)) {
$value = implode(' ', array_keys(array_filter($value)));
}
$tag .= "='{$value}'";
}
}
}
$tag .= '>';
$items[] = $tag;
}
return implode(' ', $items);
}
public function getAllowedHtml($includeGlobal = TRUE) {
$restrictions = $this
->getHtmlRestrictions();
if (!$includeGlobal) {
unset($restrictions['allowed']['*']);
}
return static::tagsToString($restrictions['allowed']);
}
public function getAllowedTags() {
$restrictions = $this
->getHtmlRestrictions();
unset($restrictions['allowed']['*']);
return array_keys($restrictions['allowed']);
}
public function getHTMLRestrictions() {
if ($this->restrictions) {
return $this->restrictions;
}
$activeTheme = \Drupal::theme()
->getActiveTheme();
$parser = $this
->getParser();
$allowedHtmlPlugins = $parser ? AllowedHtmlManager::create()
->appliesTo($parser, $activeTheme) : [];
$cacheTags = $parser ? $parser
->getCacheTags() : [];
$cid = 'markdown_allowed_html:' . Crypt::hashBase64(serialize(array_merge($cacheTags, $allowedHtmlPlugins)));
$discoveryCache = \Drupal::cache('discovery');
if (($cached = $discoveryCache
->get($cid)) && !empty($cached->data)) {
$this->restrictions = $cached->data;
return $this->restrictions;
}
$restrictions = parent::getHTMLRestrictions();
$originalGlobalAttributes = $restrictions['allowed']['*'];
unset($restrictions['allowed']['*']);
$addedGlobalAttributes = [];
if (isset($restrictions['allowed'][static::ASTERISK_PLACEHOLDER])) {
$addedGlobalAttributes['*'] = $restrictions['allowed'][static::ASTERISK_PLACEHOLDER];
$addedGlobalAttributes = static::normalizeTags($addedGlobalAttributes);
unset($restrictions['allowed'][static::ASTERISK_PLACEHOLDER]);
}
$normalizedTags = static::normalizeTags($restrictions['allowed']);
foreach ($allowedHtmlPlugins as $plugin_id => $allowedHtml) {
$tags = $allowedHtml
->allowedHtmlTags($parser, $activeTheme);
if (isset($tags['*'])) {
$addedGlobalAttributes = static::mergeAllowedTags($addedGlobalAttributes, [
'*' => $tags['*'],
]);
unset($tags['*']);
}
$normalizedTags = static::mergeAllowedTags($normalizedTags, $tags);
}
$restrictions['allowed'] = $normalizedTags;
$restrictions['allowed']['*'] = $originalGlobalAttributes;
if (!empty($addedGlobalAttributes['*'])) {
$restrictions['allowed']['*'] += $addedGlobalAttributes['*'];
}
$discoveryCache
->set($cid, $restrictions, CacheBackendInterface::CACHE_PERMANENT, $cacheTags);
$this->restrictions = $restrictions;
return $restrictions;
}
}