View source
<?php
namespace Drupal\xbbcode\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\xbbcode\Parser\Tree\ElementInterface;
use Drupal\xbbcode\Parser\Tree\NodeElementInterface;
use Drupal\xbbcode\Parser\Tree\TagElementInterface;
use Drupal\xbbcode\Parser\Tree\TextElement;
use Drupal\xbbcode\Parser\XBBCodeParser;
use Drupal\xbbcode\Plugin\TagPluginInterface;
use Drupal\xbbcode\TagPluginManager;
use Drupal\xbbcode\TagProcessResult;
use Drupal\xbbcode\XssEscape;
use Symfony\Component\DependencyInjection\ContainerInterface;
class XBBCodeFilter extends FilterBase implements ContainerFactoryPluginInterface {
protected $storage;
protected $manager;
protected $tags;
protected $tagSet;
protected $parser;
protected $cacheTags = [];
public function __construct(array $configuration, string $plugin_id, $plugin_definition, EntityStorageInterface $storage, TagPluginManager $manager) {
$this->storage = $storage;
$this->manager = $manager;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('entity_type.manager')
->getStorage('xbbcode_tag_set'), $container
->get('plugin.manager.xbbcode'));
}
public function setConfiguration(array $configuration) : XBBCodeFilter {
parent::setConfiguration($configuration);
if ($this->settings['tags'] && ($this->tagSet = $this->storage
->load($this->settings['tags']))) {
$this->tags = $this->tagSet
->getPluginCollection();
$this->cacheTags = $this->tagSet
->getCacheTags();
}
else {
$this->tags = $this->manager
->getDefaultCollection();
$this->cacheTags = [
'xbbcode_tag_new',
];
}
$this->parser = new XBBCodeParser($this->tags);
return $this;
}
public function settingsForm(array $form, FormStateInterface $form_state) : array {
$form['linebreaks'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Convert linebreaks to HTML.'),
'#default_value' => $this->settings['linebreaks'],
'#description' => $this
->t('Newline <code>\\n</code> characters will become <code><br /></code> tags.'),
];
$form['xss'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Restrict unsafe HTML by escaping.'),
'#default_value' => $this->settings['xss'],
'#description' => $this
->t('Do not disable this feature unless it interferes with other filters. Disabling it can make your site vulnerable to script injection, unless HTML is already restricted by other filters.'),
];
$options = [];
foreach ($this->storage
->loadMultiple() as $id => $tag) {
$options[$id] = $tag
->label();
}
$form['tags'] = [
'#type' => 'select',
'#title' => $this
->t('Tag set'),
'#empty_value' => '',
'#default_value' => $this->settings['tags'],
'#options' => $options,
'#description' => $this
->t('Without a <a href=":url">tag set</a>, this filter will use all available tags with default settings.', [
':url' => Url::fromRoute('entity.xbbcode_tag_set.collection')
->toString(),
]),
];
if ($collisions = $this->manager
->getDefaultNameCollisions()) {
$form['collisions'] = [
'#theme' => 'item_list',
'#items' => array_map(static function ($x) {
return "[{$x}]";
}, array_keys($collisions)),
'#prefix' => $this
->t('The following default names are each used by multiple plugins. A tag set is needed to assign unique names; otherwise each name will be assigned to one of its plugins arbitrarily.'),
];
}
return $form;
}
public function tips($long = FALSE) : string {
if ($long) {
$output = $this->tags
->getTable();
$output['#caption'] = $this
->t('You may use the following BBCode tags:');
}
else {
$output = $this->tags
->getSummary();
$output['#prefix'] = $this
->t('You may use the following BBCode tags:') . ' ';
}
$output['#cache']['tags'] = $this->cacheTags;
$output = \Drupal::service('renderer')
->render($output);
return $output;
}
public function prepare($text, $langcode) : string {
$tree = $this->parser
->parse($text);
return static::doPrepare($tree);
}
public function process($text, $langcode) : FilterProcessResult {
$tree = $this->parser
->parse($text);
if ($this->settings['xss']) {
static::filterXss($tree);
}
static::decodeHtml($tree);
if ($this->settings['linebreaks']) {
static::addLinebreaks($tree);
}
$output = $tree
->render();
$result = new FilterProcessResult($output);
$result
->addCacheTags($this->cacheTags);
foreach ($tree
->getRenderedChildren() as $child) {
if ($child instanceof TagProcessResult) {
$result = $result
->merge($child);
}
}
return $result;
}
public static function doPrepare(ElementInterface $node) : string {
if ($node instanceof NodeElementInterface) {
$content = [];
foreach ($node
->getChildren() as $child) {
$content[] = static::doPrepare($child);
}
$content = implode('', $content);
if ($node instanceof TagElementInterface) {
$processor = $node
->getProcessor();
if ($processor instanceof TagPluginInterface) {
$content = $processor
->prepare($content, $node);
}
return "[{$node->getOpeningName()}{$node->getArgument()}]{$content}[/{$node->getClosingName()}]";
}
return $content;
}
return $node
->render();
}
public static function decodeHtml(NodeElementInterface $tree) : void {
$filter = static function (string $text) : string {
if (!preg_match('/[<>"\']/', $text)) {
$text = Html::decodeEntities($text);
}
return $text;
};
foreach ($tree
->getDescendants() as $node) {
if ($node instanceof TagElementInterface) {
$node
->setOption($filter($node
->getOption()));
$node
->setAttributes(array_map($filter, $node
->getAttributes()));
$node
->setSource($filter($node
->getSource()));
}
}
}
public static function filterXss(NodeElementInterface $tree) : void {
foreach ($tree
->getDescendants() as $node) {
if ($node instanceof TextElement) {
$node
->setText(XssEscape::filterAdmin($node
->getText()));
}
if ($node instanceof TagElementInterface) {
$node
->setSource(XssEscape::filterAdmin($node
->getSource()));
}
}
}
public static function addLinebreaks(NodeElementInterface $tree) : void {
foreach ($tree
->getDescendants() as $node) {
if ($node instanceof TextElement) {
$node
->setText(nl2br($node
->getText()));
}
}
}
}