class FilterCaption

Same name and namespace in other branches
  1. 9 core/modules/filter/src/Plugin/Filter/FilterCaption.php \Drupal\filter\Plugin\Filter\FilterCaption
  2. 8.9.x core/modules/filter/src/Plugin/Filter/FilterCaption.php \Drupal\filter\Plugin\Filter\FilterCaption
  3. 11.x core/modules/filter/src/Plugin/Filter/FilterCaption.php \Drupal\filter\Plugin\Filter\FilterCaption

Provides a filter to caption elements.

When used in combination with the filter_align filter, this must run last.

Attributes

#[Filter(id: "filter_caption", title: new TranslatableMarkup("Caption images"), description: new TranslatableMarkup("Uses a <code>data-caption</code> attribute on <code>&lt;img&gt;</code> tags to caption images."), type: FilterInterface::TYPE_TRANSFORM_REVERSIBLE)]

Hierarchy

Expanded class hierarchy of FilterCaption

File

core/modules/filter/src/Plugin/Filter/FilterCaption.php, line 21

Namespace

Drupal\filter\Plugin\Filter
View source
class FilterCaption extends FilterBase implements ContainerFactoryPluginInterface {
  
  /**
   * Filter manager.
   *
   * @var \Drupal\filter\FilterPluginManager
   */
  protected $filterManager;
  
  /**
   * Constructs a new FilterCaption.
   *
   * @param array $configuration
   *   Configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Definition.
   * @param \Drupal\filter\FilterPluginManager $filter_manager
   *   Filter plugin manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ?FilterPluginManager $filter_manager = NULL) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->filterManager = $filter_manager ?: \Drupal::service('plugin.manager.filter');
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container->get('plugin.manager.filter'));
  }
  
  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode) {
    $result = new FilterProcessResult($text);
    if (stristr($text, 'data-caption') !== FALSE) {
      $dom = Html::load($text);
      $xpath = new \DOMXPath($dom);
      $html_filter = $this->filterManager
        ->createInstance('filter_html', [
        'settings' => [
          'allowed_html' => '<a href hreflang target rel> <em> <strong> <cite> <code> <br>',
          'filter_html_help' => FALSE,
          'filter_html_nofollow' => FALSE,
        ],
      ]);
      foreach ($xpath->query('//*[@data-caption]') as $node) {
        // Read the data-caption attribute's value, then delete it.
        $caption = Html::escape($node->getAttribute('data-caption'));
        $node->removeAttribute('data-caption');
        // Sanitize caption: decode HTML encoding, limit allowed HTML tags; only
        // allow inline tags that are allowed by default, plus <br>.
        $caption = Html::decodeEntities($caption);
        $raw_caption = $caption;
        $filtered_caption = $html_filter->process($caption, $langcode);
        $result->addCacheableDependency($filtered_caption);
        $caption = FilteredMarkup::create($filtered_caption->getProcessedText());
        // The caption must be non-empty - however the Media Embed CKEditor
        // plugin uses a single space to represent a newly added caption. The
        // HTML filter will transform this into an empty string and prevent the
        // content editor from adding a new caption. To allow for this we treat
        // a raw caption value of ' ' as valid and adding the wrapping figure
        // element.
        // @see core/modules/media/js/plugins/drupalmedia/plugin.js
        if (mb_strlen($caption) === 0 && $raw_caption !== ' ') {
          continue;
        }
        // Given the updated node and caption: re-render it with a caption, but
        // bubble up the value of the class attribute of the captioned element,
        // this allows it to collaborate with e.g. the filter_align filter.
        $tag = $node->tagName;
        $classes = $node->getAttribute('class');
        $node->removeAttribute('class');
        $node = $node->parentNode->tagName === 'a' ? $node->parentNode : $node;
        $filter_caption = [
          '#theme' => 'filter_caption',
          // We pass the unsanitized string because this is a text format
          // filter, and after filtering, we always assume the output is safe.
          // @see \Drupal\filter\Element\ProcessedText::preRenderText()
'#node' => FilteredMarkup::create($node->C14N()),
          '#tag' => $tag,
          '#caption' => $caption,
          '#classes' => $classes,
        ];
        $altered_html = \Drupal::service('renderer')->render($filter_caption);
        // Load the altered HTML into a new DOMDocument and retrieve the element.
        $updated_nodes = Html::load($altered_html)->getElementsByTagName('body')
          ->item(0)->childNodes;
        foreach ($updated_nodes as $updated_node) {
          // Import the updated node from the new DOMDocument into the original
          // one, importing also the child nodes of the updated node.
          $updated_node = $dom->importNode($updated_node, TRUE);
          $node->parentNode
            ->insertBefore($updated_node, $node);
        }
        // Finally, remove the original data-caption node.
        $node->parentNode
          ->removeChild($node);
      }
      $result->setProcessedText(Html::serialize($dom))
        ->addAttachments([
        'library' => [
          'filter/caption',
        ],
      ]);
    }
    return $result;
  }
  
  /**
   * {@inheritdoc}
   */
  public function tips($long = FALSE) {
    if ($long) {
      return $this->t('
        <p>You can caption images, videos, blockquotes, and so on. Examples:</p>
        <ul>
            <li><code>&lt;img src="" data-caption="This is a caption" /&gt;</code></li>
            <li><code>&lt;video src="" data-caption="The Drupal Dance" /&gt;</code></li>
            <li><code>&lt;blockquote data-caption="Dries Buytaert"&gt;Drupal is awesome!&lt;/blockquote&gt;</code></li>
            <li><code>&lt;code data-caption="Hello world in JavaScript."&gt;alert("Hello world!");&lt;/code&gt;</code></li>
        </ul>');
    }
    else {
      return $this->t('You can caption images (<code>data-caption="Text"</code>), but also videos, blockquotes, and so on.');
    }
  }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
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&#039;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&#039;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
FilterBase::settingsForm public function Generates a filter&#039;s settings form. Overrides FilterInterface::settingsForm 3
FilterCaption::$filterManager protected property Filter manager.
FilterCaption::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
FilterCaption::process public function Performs the filter processing. Overrides FilterInterface::process
FilterCaption::tips public function Generates a filter&#039;s tip. Overrides FilterBase::tips
FilterCaption::__construct public function Constructs a new FilterCaption. Overrides FilterBase::__construct
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.
PluginInspectionInterface::getPluginDefinition public function Gets the definition of the plugin implementation. 6
PluginInspectionInterface::getPluginId public function Gets the plugin ID of the plugin instance. 2

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.