You are here

class XBBCodeFilter in Extensible BBCode 8.2

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

Provides a filter that converts BBCode to HTML.

Plugin annotation


@Filter(
  id = "xbbcode",
  title = @Translation("Convert BBCode into HTML."),
  type = Drupal\filter\Plugin\FilterInterface::TYPE_MARKUP_LANGUAGE,
  settings = {
    "override" = FALSE,
    "linebreaks" = TRUE,
    "tags" = {}
  }
)

Hierarchy

Expanded class hierarchy of XBBCodeFilter

File

src/Plugin/Filter/XBBCodeFilter.php, line 33
Contains Drupal\xbbcode\Plugin\Filter\XBBCodeFilter.

Namespace

Drupal\xbbcode\Plugin\Filter
View source
class XBBCodeFilter extends FilterBase {
  private $tags;

  /**
   * Construct a filter object from a bundle of tags, and the format ID.
   *
   * @param $tags
   *   Tag array.
   * @param $format
   *   Text format ID.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    module_load_include('inc', 'xbbcode');
    $this->tag_settings = $this->settings['override'] ? $this->settings['tags'] : Drupal::config('xbbcode.settings')
      ->get('tags');
    $this->tags = _xbbcode_build_tags($this->tag_settings ? $this->tag_settings : []);
  }

  /**
   * Settings callback for the filter settings of xbbcode.
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $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> characters.'),
    ];
    $form['override'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Override the <a href="@url">global settings</a> with specific settings for this format.', [
        '@url' => Drupal::url('xbbcode.admin_handlers'),
      ]),
      '#default_value' => $this->settings['override'],
      '#description' => $this
        ->t('Overriding the global settings allows you to disallow or allow certain special tags for this format, while other formats will not be affected by the change.'),
      '#attributes' => [
        'onchange' => 'Drupal.toggleFieldset(jQuery("#edit-filters-xbbcode-settings-tags"))',
      ],
    ];
    $form = XBBCodeHandlerForm::buildFormHandlers($form, $this->tag_settings);
    $form['handlers']['#type'] = 'details';
    $form['handlers']['#open'] = $this->settings['override'];
    $parents = $form['#parents'];
    $parents[] = 'tags';
    $form['handlers']['tags']['#parents'] = $parents;
    $form['handlers']['extra']['tags']['#parents'] = $parents;
    return $form;
  }
  public function tips($long = FALSE) {
    if (!$this->tags) {
      return $this
        ->t('BBCode is enabled, but no tags are defined.');
    }
    if ($long) {
      $table = [
        '#type' => 'table',
        '#caption' => $this
          ->t('Allowed BBCode tags:'),
        '#header' => [
          $this
            ->t('Tag Description'),
          $this
            ->t('You Type'),
          $this
            ->t('You Get'),
        ],
      ];
      foreach ($this->tags as $name => $tag) {
        $table[$name] = [
          [
            '#markup' => "<strong>[{$name}]</strong><br />" . $tag->description,
            '#attributes' => [
              'class' => [
                'description',
              ],
            ],
          ],
          [
            '#markup' => '<code>' . str_replace("\n", '<br />', SafeMarkup::checkPlain($tag->sample)) . '</code>',
            '#attributes' => [
              'class' => [
                'type',
              ],
            ],
          ],
          [
            '#markup' => $this
              ->process($tag->sample, NULL)
              ->getProcessedText(),
            '#attributes' => [
              'class' => [
                'get',
              ],
            ],
          ],
        ];
      }
      return Drupal::service('renderer')
        ->render($table);
    }
    else {
      foreach ($this->tags as $name => $tag) {
        $tags[$name] = '<abbr title="' . $tag->description . '">[' . $name . ']</abbr>';
      }
      return $this
        ->t('You may use these tags: !tags', [
        '!tags' => implode(', ', $tags),
      ]);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode) {
    $tree = $this
      ->buildTree($text);
    $output = $this
      ->renderTree($tree->content);

    // 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']) {
      $output = nl2br($output);
    }
    return new FilterProcessResult($output);
  }
  private function buildTree($text) {

    // Find all opening and closing tags in the text.
    preg_match_all(XBBCODE_RE_TAG, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);

    // Initialize the name tracker, and the list of valid tags.
    $open_by_name = [];
    $tags = [];
    foreach ($matches as $match) {
      $tag = new XBBCodeTagMatch($match);
      if (isset($this->tags[$tag->name])) {
        $tag->selfclosing = $this->tags[$tag->name]->options->selfclosing;
        $tags[] = $tag;
        $open_by_name[$tag->name] = 0;
      }
    }

    // Initialize the stack with a root element.
    $stack = [
      new XBBCodeRootElement(),
    ];
    foreach ($tags as $tag) {

      // Add text before the new tag to the parent
      end($stack)
        ->advance($text, $tag->start);

      // Case 1: The tag is opening and not self-closing.
      if (!$tag->closing && !$tag->selfclosing) {

        // Stack the open tag, and increment the tracker.
        array_push($stack, $tag);
        $open_by_name[$tag->name]++;
      }
      elseif ($tag->selfclosing) {
        end($stack)
          ->append($tag, $tag->end);
      }
      elseif ($open_by_name[$tag->name]) {
        $open_by_name[$tag->name]--;

        // Find the last matching opening tag, breaking any unclosed tag since then.
        while (end($stack)->name != $tag->name) {
          $dangling = array_pop($stack);
          end($stack)
            ->breakTag($dangling);
          $open_by_name[$dangling->name]--;
        }
        $current = array_pop($stack);
        $current
          ->advance($text, $tag->start);
        $current->source = substr($text, $current->end, $current->offset - $current->end);
        $current->closer = $tag;
        end($stack)
          ->append($current, $tag->end);
      }
    }

    // Add the remainder of the text, and then break any tags still open.
    end($stack)
      ->advance($text, strlen($text));
    while (count($stack) > 1) {
      $dangling = array_pop($stack);
      end($stack)
        ->breakTag($dangling);
    }
    return end($stack);
  }
  private function renderTree($tree) {
    $output = '';
    foreach ($tree as $root) {
      if (is_object($root)) {
        $root->content = $this
          ->renderTree($root->content);
        $rendered = $this
          ->renderTag($root);
        $root = $rendered !== NULL ? $rendered : $root
          ->getOuterText();
      }
      $output .= $root;
    }
    return $output;
  }

  /**
   * Render a single tag.
   *
   * @param $tag
   *   The complete match object, including its name, content and attributes.
   *
   * @return
   *   HTML code to insert in place of the tag and its content.
   */
  private function renderTag(XBBCodeTagMatch $tag) {
    if ($callback = $this->tags[$tag->name]->callback) {
      return $callback($tag);
    }
    else {
      $replace['{content}'] = $tag->content;
      $replace['{source}'] = $tag->source;
      $replace['{option}'] = $tag->option;
      foreach ($tag->attrs as $name => $value) {
        $replace['{' . $name . '}'] = $value;
      }
      $markup = str_replace(array_keys($replace), array_values($replace), $this->tags[$tag->name]->markup);

      // Make sure that unset placeholders are replaced with empty strings.
      $markup = preg_replace('/{\\w+}/', '', $markup);
      return $markup;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
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
FilterBase::prepare public function Prepares the text for processing. Overrides FilterInterface::prepare
FilterBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration 1
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. 29
MessengerTrait::messenger public function Gets the messenger. 29
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 3
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. 1
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::$tags private property
XBBCodeFilter::buildTree private function
XBBCodeFilter::process public function Performs the filter processing. Overrides FilterInterface::process
XBBCodeFilter::renderTag private function Render a single tag.
XBBCodeFilter::renderTree private function
XBBCodeFilter::settingsForm public function Settings callback for the filter settings of xbbcode. Overrides FilterBase::settingsForm
XBBCodeFilter::tips public function Generates a filter's tip. Overrides FilterBase::tips
XBBCodeFilter::__construct public function Construct a filter object from a bundle of tags, and the format ID. Overrides FilterBase::__construct