Creating a Custom Filter

Drupal 8
Drupal 9
Drupal 10
Basic
Text formats and editors
WYSIWYG
Text editor

Text formats include an ordered list of filters which can serve different purposes such as limitation what HTML can be placed into content, replacing a pattern, adding extra CSS to the page, etc. The filters are invoked in order if they are used to filter text.

Text formats and editorsArrowText editor enabled filters

 

Starting from Drupal 8 new filters can be defined using plugins with specification of their type.

Filter plugins \Drupal\filter\Plugin\FilterInterface

  1. The plugin should be located into the proper place to adhere to PSR-4 standards. The folder structure should be /src/Plugin/Filter.
  2. The plugin should be annotated with the @Filter annotation.
  3. The plugin should specify a filter type in its annotation:
    • FilterInterface::TYPE_HTML_RESTRICTOR - HTML tag and attribute restricting filters.
    • FilterInterface::TYPE_MARKUP_LANGUAGE - The filter converts something that's not HTML to HTML in a way that is not compatible with WYSIWYG editing.
    • FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE - Irreversible transformation filters. The filter performs a transform for which a WYSIWYG plugin does not exist to perform the transformation client-side
    • FilterInterface::TYPE_TRANSFORM_REVERSIBLE - Reversible transformation filters. The filter performs a transform for which a WYSIWYG plugin exists to perform the same transformation (and its reverse) client-side.
  4. \Drupal\filter\Plugin\FilterBase provides a default implementation so the plugin don't need to implement every method.

Example filter plugin "Wrap table"

Let's build an example filter plugin to provide a filter to wrap a table element to an extra wrapper with the class "tablefield-wrapper" to improve its displaying on different devices. We plan on taking existing HTML markup and a little modernise it, so the type FilterInterface::TYPE_TRANSFORM_REVERSIBLE fits.

namespace Drupal\MODULE_NAME\Plugin\Filter;

use Drupal\Component\Utility\Html;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;

/**
 * Provides a filter to wrap a table into an extra wrapper.
 *
 * @Filter(
 *   id = "MODULE_NAME_table_wrap",
 *   title = @Translation("Wrap tables"),
 *   description = @Translation("Uses an extra <code>div</code> tag to wrap tables."),
 *   type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
 * )
 */
class FilterTableWrap extends FilterBase {

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode) {
    $result = new FilterProcessResult($text);

    // If there are no tables, return early.
    if (stripos($text, '<table') === FALSE) {
      return $result;
    }

    return $result->setProcessedText($this->wrapTables($text));
  }

  /**
   * Transform markup of tables to add the wrapper.
   *
   * @param string $text
   *   The markup to transform.
   *
   * @return string
   *   The transformed text.
   */
  private function wrapTables(string $text): string {
    $dom = Html::load($text);

    // Create a wrapper element.
    $wrapper = $dom->createElement('div');
    $wrapper->setAttribute('class', 'tablefield-wrapper');

    $tables = $dom->getElementsByTagName('table');
    foreach ($tables as $table) {
      // Clone the wrapper div.
      $wrapper_clone = $wrapper->cloneNode();
      // Replace the table with this wrapper div.
      $table->parentNode->replaceChild($wrapper_clone, $table);
      // Append the table to wrapper div.
      $wrapper_clone->appendChild($table);
    }

    return Html::serialize($dom);
  }

}

We can add this new filter "Wrap tables" via text editor admin UI:

Text editor with the new Wrap tables filter

After this we can add a table using the text editor in usual way:

Adding table using the text editor

But the table will have an extra div wrapper with the specific class on front-end:

Table displaying on FE

References