You are here

class XBBCodeFilter in Extensible BBCode 4.0.x

Same name and namespace in other branches
  1. 8.3 src/Plugin/Filter/XBBCodeFilter.php \Drupal\xbbcode\Plugin\Filter\XBBCodeFilter
  2. 8.2 src/Plugin/Filter/XBBCodeFilter.php \Drupal\xbbcode\Plugin\Filter\XBBCodeFilter

Provides a filter that converts BBCode to HTML.

Plugin annotation


@Filter(
  id = "xbbcode",
  module = "xbbcode",
  title = @Translation("Extensible BBCode"),
  description = @Translation("Render <code>[bbcode]</code> tags to HTML."),
  type = Drupal\filter\Plugin\FilterInterface::TYPE_MARKUP_LANGUAGE,
  settings = {
    "linebreaks" = TRUE,
    "tags" = "",
    "xss" = TRUE,
  }
)

Hierarchy

Expanded class hierarchy of XBBCodeFilter

1 file declares its use of XBBCodeFilter
TagFormBase.php in src/Form/TagFormBase.php

File

src/Plugin/Filter/XBBCodeFilter.php, line 39

Namespace

Drupal\xbbcode\Plugin\Filter
View source
class XBBCodeFilter extends FilterBase implements ContainerFactoryPluginInterface {

  /**
   * The tag set storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $storage;

  /**
   * The tag plugin manager.
   *
   * @var \Drupal\xbbcode\TagPluginManager
   */
  protected $manager;

  /**
   * The tag plugins.
   *
   * @var \Drupal\xbbcode\TagPluginCollection
   */
  protected $tags;

  /**
   * The tag set (optional).
   *
   * @var \Drupal\xbbcode\Entity\TagSetInterface
   */
  protected $tagSet;

  /**
   * The parser.
   *
   * @var \Drupal\xbbcode\Parser\ParserInterface
   */
  protected $parser;

  /**
   * The cache tags that invalidate this filter.
   *
   * @var string[]
   */
  protected $cacheTags = [];

  /**
   * XBBCodeFilter constructor.
   *
   * @param array $configuration
   *   Plugin configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin definition.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The tag set storage.
   * @param \Drupal\xbbcode\TagPluginManager $manager
   *   The tag plugin manager.
   */
  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);
  }

  /**
   * {@inheritdoc}
   */
  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'));
  }

  /**
   * {@inheritdoc}
   */
  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();

      // Without a tag set, invalidate it when any custom tag is created.
      $this->cacheTags = [
        'xbbcode_tag_new',
      ];
    }
    $this->parser = new XBBCodeParser($this->tags);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  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>&lt;br /&gt;</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;
  }

  /**
   * {@inheritdoc}
   */
  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;

    // @todo Remove once FilterInterface::tips() is modernized.
    $output = \Drupal::service('renderer')
      ->render($output);
    return $output;
  }

  /**
   * {@inheritdoc}
   */
  public function prepare($text, $langcode) : string {
    $tree = $this->parser
      ->parse($text);
    return static::doPrepare($tree);
  }

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode) : FilterProcessResult {
    $tree = $this->parser
      ->parse($text);
    if ($this->settings['xss']) {
      static::filterXss($tree);
    }

    // Reverse any HTML filtering in attribute and option strings.
    static::decodeHtml($tree);

    // The core AutoP filter breaks inline tags that span multiple paragraphs.
    // Since there is no advantage in using <p></p> tags, this filter uses
    // ordinary <br /> tags which are usable inside inline tags.
    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;
  }

  /**
   * Recursively apply source transformations to each tag element.
   *
   * @param \Drupal\xbbcode\Parser\Tree\ElementInterface $node
   *   The parse tree.
   *
   * @return string
   *   The fully prepared source.
   */
  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();
  }

  /**
   * Reverse HTML encoding that other filters may have applied.
   *
   * The "option" and "attribute" values are provided to plugins as raw input
   * (and will be filtered by them before printing).
   *
   * @param \Drupal\xbbcode\Parser\Tree\NodeElementInterface $tree
   *   The parse tree.
   */
  public static function decodeHtml(NodeElementInterface $tree) : void {
    $filter = static function (string $text) : string {

      // If the string is free of raw HTML, decode its entities.
      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()));
      }
    }
  }

  /**
   * Escape unsafe markup in text elements and the source of tag elements.
   *
   * This is a safety feature that allows the BBCode processor to be used
   * on its own (without HTML restrictors) while still maintaining
   * markup safety.
   *
   * @param \Drupal\xbbcode\Parser\Tree\NodeElementInterface $tree
   *   The parse tree.
   */
  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()));
      }
    }
  }

  /**
   * Add linebreaks inside text elements.
   *
   * @param \Drupal\xbbcode\Parser\Tree\NodeElementInterface $tree
   *   The parse tree.
   */
  public static function addLinebreaks(NodeElementInterface $tree) : void {
    foreach ($tree
      ->getDescendants() as $node) {
      if ($node instanceof TextElement) {
        $node
          ->setText(nl2br($node
          ->getText()));
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
FilterBase::$provider public property The name of the provider that owns this filter.
FilterBase::$settings public property An associative array containing the configured settings of this filter.
FilterBase::$status public property A Boolean indicating whether this filter is enabled.
FilterBase::$weight public property The weight of this filter compared to others in a filter collection.
FilterBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 1
FilterBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration
FilterBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
FilterBase::getDescription public function Returns the administrative description for this filter plugin. Overrides FilterInterface::getDescription
FilterBase::getHTMLRestrictions public function Returns HTML allowed by this filter's configuration. Overrides FilterInterface::getHTMLRestrictions 4
FilterBase::getLabel public function Returns the administrative label for this filter plugin. Overrides FilterInterface::getLabel
FilterBase::getType public function Returns the processing type of this filter plugin. Overrides FilterInterface::getType
FilterInterface::TYPE_HTML_RESTRICTOR constant HTML tag and attribute restricting filters to prevent XSS attacks.
FilterInterface::TYPE_MARKUP_LANGUAGE constant Non-HTML markup language filters that generate HTML.
FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE constant Irreversible transformation filters.
FilterInterface::TYPE_TRANSFORM_REVERSIBLE constant Reversible transformation filters.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
XBBCodeFilter::$cacheTags protected property The cache tags that invalidate this filter.
XBBCodeFilter::$manager protected property The tag plugin manager.
XBBCodeFilter::$parser protected property The parser.
XBBCodeFilter::$storage protected property The tag set storage.
XBBCodeFilter::$tags protected property The tag plugins.
XBBCodeFilter::$tagSet protected property The tag set (optional).
XBBCodeFilter::addLinebreaks public static function Add linebreaks inside text elements.
XBBCodeFilter::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
XBBCodeFilter::decodeHtml public static function Reverse HTML encoding that other filters may have applied.
XBBCodeFilter::doPrepare public static function Recursively apply source transformations to each tag element.
XBBCodeFilter::filterXss public static function Escape unsafe markup in text elements and the source of tag elements.
XBBCodeFilter::prepare public function Prepares the text for processing. Overrides FilterBase::prepare
XBBCodeFilter::process public function Performs the filter processing. Overrides FilterInterface::process
XBBCodeFilter::setConfiguration public function Sets the configuration for this plugin instance. Overrides FilterBase::setConfiguration
XBBCodeFilter::settingsForm public function Generates a filter's settings form. Overrides FilterBase::settingsForm
XBBCodeFilter::tips public function Generates a filter's tip. Overrides FilterBase::tips
XBBCodeFilter::__construct public function XBBCodeFilter constructor. Overrides FilterBase::__construct