FilterCaption.php 4.23 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\filter\Plugin\Filter;

5
use Drupal\Component\Utility\Html;
6 7
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
8
use Drupal\filter\FilterProcessResult;
9
use Drupal\filter\Plugin\FilterBase;
10
use Drupal\filter\Render\FilteredMarkup;
11 12

/**
13 14 15
 * Provides a filter to caption elements.
 *
 * When used in combination with the filter_align filter, this must run last.
16 17 18
 *
 * @Filter(
 *   id = "filter_caption",
19 20
 *   title = @Translation("Caption images"),
 *   description = @Translation("Uses a <code>data-caption</code> attribute on <code>&lt;img&gt;</code> tags to caption images."),
21
 *   type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
22 23 24 25 26 27 28
 * )
 */
class FilterCaption extends FilterBase {

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

32
    if (stristr($text, 'data-caption') !== FALSE) {
33
      $dom = Html::load($text);
34
      $xpath = new \DOMXPath($dom);
35 36
      foreach ($xpath->query('//*[@data-caption]') as $node) {
        // Read the data-caption attribute's value, then delete it.
37
        $caption = Html::escape($node->getAttribute('data-caption'));
38
        $node->removeAttribute('data-caption');
39

40 41
        // Sanitize caption: decode HTML encoding, limit allowed HTML tags; only
        // allow inline tags that are allowed by default, plus <br>.
42
        $caption = Html::decodeEntities($caption);
43
        $caption = FilteredMarkup::create(Xss::filter($caption, array('a', 'em', 'strong', 'cite', 'code', 'br')));
44

45 46
        // The caption must be non-empty.
        if (Unicode::strlen($caption) === 0) {
47 48 49
          continue;
        }

50 51 52
        // 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.
53
        $tag = $node->tagName;
54 55
        $classes = $node->getAttribute('class');
        $node->removeAttribute('class');
56
        $node = ($node->parentNode->tagName === 'a') ? $node->parentNode : $node;
57 58
        $filter_caption = array(
          '#theme' => 'filter_caption',
59 60 61
          // 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()
62
          '#node' => FilteredMarkup::create($node->C14N()),
63
          '#tag' => $tag,
64
          '#caption' => $caption,
65
          '#classes' => $classes,
66
        );
67
        $altered_html = \Drupal::service('renderer')->render($filter_caption);
68 69

        // Load the altered HTML into a new DOMDocument and retrieve the element.
70
        $updated_nodes = Html::load($altered_html)->getElementsByTagName('body')
71
          ->item(0)
72
          ->childNodes;
73

74 75 76 77 78 79 80 81
        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);
82 83
      }

84
      $result->setProcessedText(Html::serialize($dom))
85
        ->addAttachments(array(
86 87 88 89
          'library' => array(
            'filter/caption',
          ),
        ));
90 91
    }

92
    return $result;
93 94 95 96 97 98 99
  }

  /**
   * {@inheritdoc}
   */
  public function tips($long = FALSE) {
    if ($long) {
100
      return $this->t('
101
        <p>You can caption images, videos, blockquotes, and so on. Examples:</p>
102
        <ul>
103 104 105 106
            <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>
107 108 109
        </ul>');
    }
    else {
110
      return $this->t('You can caption images (<code>data-caption="Text"</code>), but also videos, blockquotes, and so on.');
111 112
    }
  }
113

114
}