Commit 008fb117 authored by alexpott's avatar alexpott

Issue #2239413 by Wim Leers, blueminds: Split FilterCaption into FilterAlign...

Issue #2239413 by Wim Leers, blueminds: Split FilterCaption into FilterAlign (for data-align) and FilterCaption (for data-caption).
parent 9af55ec1
......@@ -58,6 +58,12 @@ function ckeditor_ckeditor_css_alter(array &$css, Editor $editor) {
// CKEditor DrupalImageCaption plugin.
if ($editor->getFilterFormat()->filters('filter_caption')->status) {
$css[] = drupal_get_path('module', 'filter') . '/css/filter.caption.css';
}
// Add the filter caption CSS if the text format associated with this text
// editor uses the filter_align filter. This is used by the included
// CKEditor DrupalImageCaption plugin.
if ($editor->getFilterFormat()->filters('filter_align')->status) {
$css[] = drupal_get_path('module', 'ckeditor') . '/css/plugins/drupalimagecaption/ckeditor.drupalimagecaption.css';
}
}
......
......@@ -33,6 +33,10 @@
return;
}
// Only perform the downcasting/upcasting for to the enabled filters.
var captionFilterEnabled = editor.config.drupalImageCaption_captionFilterEnabled;
var alignFilterEnabled = editor.config.drupalImageCaption_alignFilterEnabled;
// Override default features definitions for drupalimagecaption.
CKEDITOR.tools.extend(widgetDefinition.features, {
caption: {
......@@ -63,13 +67,17 @@
var captionHtml = caption && caption.getData();
var attrs = img.attributes;
// If image contains a non-empty caption, serialize caption to the
// data-caption attribute.
if (captionHtml) {
attrs['data-caption'] = captionHtml;
if (captionFilterEnabled) {
// If image contains a non-empty caption, serialize caption to the
// data-caption attribute.
if (captionHtml) {
attrs['data-caption'] = captionHtml;
}
}
if (this.data.align !== 'none') {
attrs['data-align'] = this.data.align;
if (alignFilterEnabled) {
if (this.data.align !== 'none') {
attrs['data-align'] = this.data.align;
}
}
attrs['data-editor-file-uuid'] = this.data['data-editor-file-uuid'];
......@@ -96,60 +104,69 @@
// We won't need the attributes during editing: we'll use widget.data
// to store them (except the caption, which is stored in the DOM).
var caption = attrs['data-caption'];
data.align = attrs['data-align'];
if (captionFilterEnabled) {
var caption = attrs['data-caption'];
delete attrs['data-caption'];
}
if (alignFilterEnabled) {
data.align = attrs['data-align'];
delete attrs['data-align'];
}
data['data-editor-file-uuid' ] = attrs['data-editor-file-uuid'];
delete attrs['data-caption'];
delete attrs['data-align'];
delete attrs['data-editor-file-uuid'];
// Unwrap from <p> wrapper created by HTML parser for captioned image.
// Captioned image will be transformed to <figure>, so we don't want
// the <p> anymore.
if (element.parent.name === 'p' && caption) {
var index = element.getIndex();
var splitBefore = index > 0;
var splitAfter = index + 1 < element.parent.children.length;
if (splitBefore) {
element.parent.split(index);
if (captionFilterEnabled) {
// Unwrap from <p> wrapper created by HTML parser for a captioned
// image. The captioned image will be transformed to <figure>, so we
// don't want the <p> anymore.
if (element.parent.name === 'p' && caption) {
var index = element.getIndex();
var splitBefore = index > 0;
var splitAfter = index + 1 < element.parent.children.length;
if (splitBefore) {
element.parent.split(index);
}
index = element.getIndex();
if (splitAfter) {
element.parent.split(index + 1);
}
element.parent.replaceWith(element);
retElement = element;
}
index = element.getIndex();
if (splitAfter) {
element.parent.split(index + 1);
}
element.parent.replaceWith(element);
retElement = element;
}
// If this image has a caption, create a full <figure> structure.
if (caption) {
var figure = new CKEDITOR.htmlParser.element('figure');
caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
// Use Drupal's data-placeholder attribute to insert a CSS-based,
// translation-ready placeholder for empty captions. Note that it
// also must to be done for new instances (see
// widgetDefinition._createDialogSaveCallback).
caption.attributes['data-placeholder'] = placeholderText;
element.replaceWith(figure);
figure.add(element);
figure.add(caption);
figure.attributes['class'] = editor.config.image2_captionedClass;
retElement = figure;
// If this image has a caption, create a full <figure> structure.
if (caption) {
var figure = new CKEDITOR.htmlParser.element('figure');
caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
// Use Drupal's data-placeholder attribute to insert a CSS-based,
// translation-ready placeholder for empty captions. Note that it
// also must to be done for new instances (see
// widgetDefinition._createDialogSaveCallback).
caption.attributes['data-placeholder'] = placeholderText;
element.replaceWith(figure);
figure.add(element);
figure.add(caption);
figure.attributes['class'] = editor.config.image2_captionedClass;
retElement = figure;
}
}
// If this image doesn't have a caption, but it is centered, make sure
// that it's wrapped with <p>, which will become a part of the widget.
if (data.align === 'center' && !caption) {
var p = new CKEDITOR.htmlParser.element('p');
element.replaceWith(p);
p.add(element);
// Apply the class for centered images.
p.addClass(editor.config.image2_alignClasses[1]);
retElement = p;
if (alignFilterEnabled) {
// If this image doesn't have a caption (or the caption filter is
// disabled), but it is centered, make sure that it's wrapped with
// <p>, which will become a part of the widget.
if (data.align === 'center' && (!captionFilterEnabled || !caption)) {
var p = new CKEDITOR.htmlParser.element('p');
element.replaceWith(p);
p.add(element);
// Apply the class for centered images.
p.addClass(editor.config.image2_alignClasses[1]);
retElement = p;
}
}
// Return the upcasted element (<img>, <figure> or <p>).
......
......@@ -57,10 +57,15 @@ public function getFile() {
* {@inheritdoc}
*/
public function getConfig(Editor $editor) {
$format = $editor->getFilterFormat();
return array(
'image2_captionedClass' => 'caption caption-img',
'image2_alignClasses' => array('align-left', 'align-center', 'align-right'),
'drupalImageCaption_captionPlaceholderText' => t('Enter caption here'),
// Only enable those parts of DrupalImageCaption for which the
// corresponding Drupal text filters are enabled.
'drupalImageCaption_captionFilterEnabled' => $format->filters('filter_caption')->status,
'drupalImageCaption_alignFilterEnabled' => $format->filters('filter_align')->status,
);
}
......@@ -73,9 +78,10 @@ function isEnabled(Editor $editor) {
}
// Automatically enable this plugin if the text format associated with this
// text editor uses the filter_caption filter and the DrupalImage button is
// enabled.
if ($editor->getFilterFormat()->filters('filter_caption')->status) {
// text editor uses the filter_align or filter_caption filter and the
// DrupalImage button is enabled.
$format = $editor->getFilterFormat();
if ($format->filters('filter_align')->status || $format->filters('filter_caption')->status) {
$enabled = FALSE;
$settings = $editor->getSettings();
foreach ($settings['toolbar']['rows'] as $row) {
......
......@@ -135,9 +135,9 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
'#parents' => array('attributes', 'height'),
);
// When Drupal core's filter_caption is being used, the text editor may
// When Drupal core's filter_align is being used, the text editor may
// offer the ability to change the alignment.
if (isset($image_element['data-align'])) {
if (isset($image_element['data-align']) && $filter_format->filters('filter_align')->status) {
$form['align'] = array(
'#title' => $this->t('Align'),
'#type' => 'radios',
......@@ -156,7 +156,7 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
// When Drupal core's filter_caption is being used, the text editor may
// offer the ability to in-place edit the image's caption: show a toggle.
if (isset($image_element['hasCaption'])) {
if (isset($image_element['hasCaption']) && $filter_format->filters('filter_caption')->status) {
$form['caption'] = array(
'#title' => $this->t('Caption'),
'#type' => 'checkbox',
......
......@@ -80,7 +80,7 @@ function filter_theme() {
'node' => NULL,
'tag' => NULL,
'caption' => NULL,
'align' => NULL,
'classes' => NULL,
),
'template' => 'filter-caption',
)
......
<?php
/**
* @file
* Contains \Drupal\filter\Plugin\Filter\FilterAlign.
*/
namespace Drupal\filter\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
/**
* Provides a filter to align elements.
*
* @Filter(
* id = "filter_align",
* title = @Translation("Align images"),
* description = @Translation("Uses a <code>data-align</code> attribute on <code>&lt;img&gt;</code> tags to align images."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
class FilterAlign extends FilterBase {
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
$result = new FilterProcessResult($text);
if (stristr($text, 'data-align') !== FALSE) {
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//*[@data-align]') as $node) {
// Read the data-align attribute's value, then delete it.
$align = $node->getAttribute('data-align');
$node->removeAttribute('data-align');
// If one of the allowed alignments, add the corresponding class.
if (in_array($align, array('left', 'center', 'right'))) {
$classes = $node->getAttribute('class');
$classes = (strlen($classes) > 0) ? explode(' ', $classes) : array();
$classes[] = 'align-' . $align;
$node->setAttribute('class', implode(' ', $classes));
}
}
$result->setProcessedText(Html::serialize($dom));
}
return $result;
}
/**
* {@inheritdoc}
*/
public function tips($long = FALSE) {
if ($long) {
return $this->t('
<p>You can align images, videos, blockquotes and so on to the left, right or center. Examples:</p>
<ul>
<li>Align an image to the left: <code>&lt;img src="" data-align="left" /&gt;</code></li>
<li>Align an image to the center: <code>&lt;img src="" data-align="center" /&gt;</code></li>
<li>Align an image to the right: <code>&lt;img src="" data-align="right" /&gt;</code></li>
<li>… and you can apply this to other elements as well: <code>&lt;video src="" data-align="center" /&gt;</code></li>
</ul>');
}
else {
return $this->t('You can align images (<code>data-align="center"</code>code>), but also videos, blockquotes, and so on.');
}
}
}
......@@ -16,12 +16,14 @@
use Drupal\filter\Plugin\FilterBase;
/**
* Provides a filter to display image captions and align images.
* Provides a filter to caption elements.
*
* When used in combination with the filter_align filter, this must run last.
*
* @Filter(
* id = "filter_caption",
* title = @Translation("Display image captions and align images"),
* description = @Translation("Uses data-caption and data-align attributes on &lt;img&gt; tags to caption and align images."),
* title = @Translation("Caption images"),
* description = @Translation("Uses a <code>data-caption</code> attribute on <code>&lt;img&gt;</code> tags to caption images."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
......@@ -33,60 +35,35 @@ class FilterCaption extends FilterBase {
public function process($text, $langcode) {
$result = new FilterProcessResult($text);
if (stristr($text, 'data-caption') !== FALSE || stristr($text, 'data-align') !== FALSE) {
$caption_found = FALSE;
if (stristr($text, 'data-caption') !== FALSE) {
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//*[@data-caption or @data-align]') as $node) {
$caption = NULL;
$align = NULL;
foreach ($xpath->query('//*[@data-caption]') as $node) {
// Read the data-caption attribute's value, then delete it.
$caption = String::checkPlain($node->getAttribute('data-caption'));
$node->removeAttribute('data-caption');
// Retrieve, then remove the data-caption and data-align attributes.
if ($node->hasAttribute('data-caption')) {
$caption = String::checkPlain($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 = String::decodeEntities($caption);
$caption = Xss::filter($caption, array('a', 'em', 'strong', 'cite', 'code', 'br'));
// The caption must be non-empty.
if (Unicode::strlen($caption) === 0) {
$caption = NULL;
}
}
if ($node->hasAttribute('data-align')) {
$align = $node->getAttribute('data-align');
$node->removeAttribute('data-align');
// Only allow 3 values: 'left', 'center' and 'right'.
if (!in_array($align, array('left', 'center', 'right'))) {
$align = NULL;
}
}
// Sanitize caption: decode HTML encoding, limit allowed HTML tags; only
// allow inline tags that are allowed by default, plus <br>.
$caption = String::decodeEntities($caption);
$caption = Xss::filter($caption, array('a', 'em', 'strong', 'cite', 'code', 'br'));
// Don't transform the HTML if there isn't a caption after validation.
if ($caption === NULL) {
// If there is a valid alignment, then transform the data-align
// attribute to a corresponding alignment class.
if ($align !== NULL) {
$classes = $node->getAttribute('class');
$classes = (strlen($classes) > 0) ? explode(' ', $classes) : array();
$classes[] = 'align-' . $align;
$node->setAttribute('class', implode(' ', $classes));
}
// The caption must be non-empty.
if (Unicode::strlen($caption) === 0) {
continue;
}
else {
$caption_found = TRUE;
}
// Given the updated node, caption and alignment: re-render it with a
// caption.
// 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.
$classes = $node->getAttribute('class');
$node->removeAttribute('class');
$filter_caption = array(
'#theme' => 'filter_caption',
'#node' => SafeMarkup::set($node->C14N()),
'#tag' => $node->tagName,
'#caption' => $caption,
'#align' => $align,
'#classes' => $classes,
);
$altered_html = drupal_render($filter_caption);
......@@ -103,15 +80,12 @@ public function process($text, $langcode) {
$node->parentNode->replaceChild($updated_node, $node);
}
$result->setProcessedText(Html::serialize($dom));
if ($caption_found) {
$result->addAssets(array(
$result->setProcessedText(Html::serialize($dom))
->addAssets(array(
'library' => array(
'filter/caption',
),
));
}
}
return $result;
......@@ -123,15 +97,17 @@ public function process($text, $langcode) {
public function tips($long = FALSE) {
if ($long) {
return $this->t('
<p>You can add image captions and align images left, right or centered. Examples:</p>
<p>You can caption images, videos, blockquotes, and so on. Examples:</p>
<ul>
<li>Caption an image: <code>&lt;img src="" data-caption="This is a caption" /&gt;</code></li>
<li>Align an image: <code>&lt;img src="" data-align="center" /&gt;</code></li>
<li>Caption & align an image: <code>&lt;img src="" data-caption="Alpaca" data-align="right" /&gt;</code></li>
<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 (data-caption="Text") and align images (data-align="center"), but also video, blockquotes, and so on.');
return $this->t('You can caption images (<code>data-caption="Text"<code>), but also videos, blockquotes, and so on.');
}
}
}
......@@ -40,6 +40,61 @@ protected function setUp() {
$this->filters = $bag->getAll();
}
/**
* Tests the align filter.
*/
function testAlignFilter() {
$filter = $this->filters['filter_align'];
$test = function($input) use ($filter) {
return $filter->process($input, 'und');
};
// No data-align attribute.
$input = '<img src="llama.jpg" />';
$expected = $input;
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Data-align attribute: all 3 allowed values.
$input = '<img src="llama.jpg" data-align="left" />';
$expected = '<img src="llama.jpg" class="align-left" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="center" />';
$expected = '<img src="llama.jpg" class="align-center" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="right" />';
$expected = '<img src="llama.jpg" class="align-right" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Data-align attribute: a disallowed value.
$input = '<img src="llama.jpg" data-align="left foobar" />';
$expected = '<img src="llama.jpg" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Empty data-align attribute.
$input = '<img src="llama.jpg" data-align="" />';
$expected = '<img src="llama.jpg" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Ensure the filter also works with uncommon yet valid attribute quoting.
$input = '<img src=llama.jpg data-align=right />';
$expected = '<img src="llama.jpg" class="align-right" />';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
// Security test: attempt to inject an additional class.
$input = '<img src="llama.jpg" data-align="center another-class-here" />';
$expected = '<img src="llama.jpg" />';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
// Security test: attempt an XSS.
$input = '<img src="llama.jpg" data-align="center \'onclick=\'alert(foo);" />';
$expected = '<img src="llama.jpg" />';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
}
/**
* Tests the caption filter.
*/
......@@ -56,12 +111,12 @@ function testCaptionFilter() {
),
);
// No data-caption nor data-align attributes.
// No data-caption attribute.
$input = '<img src="llama.jpg" />';
$expected = $input;
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Only data-caption attribute.
// Data-caption attribute.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
......@@ -103,26 +158,42 @@ function testCaptionFilter() {
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Only data-align attribute: all 3 allowed values.
$input = '<img src="llama.jpg" data-align="left" />';
$expected = '<img src="llama.jpg" class="align-left" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="center" />';
$expected = '<img src="llama.jpg" class="align-center" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="right" />';
$expected = '<img src="llama.jpg" class="align-right" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Ensure the filter also works with uncommon yet valid attribute quoting.
$input = '<img src=llama.jpg data-caption=\'Loquacious llama!\' />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Only data-align attribute: a disallowed value.
$input = '<img src="llama.jpg" data-align="left foobar" />';
$expected = '<img src="llama.jpg" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Finally, ensure that this also works on any other tag.
$input = '<video src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<figure class="caption caption-video"><video src="llama.jpg"></video><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
$input = '<foobar data-caption="Loquacious llama!">baz</foobar>';
$expected = '<figure class="caption caption-foobar"><foobar>baz</foobar><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
}
// Empty data-align attribute.
$input = '<img src="llama.jpg" data-align="" />';
$expected = '<img src="llama.jpg" />';
$this->assertIdentical($expected, $test($input)->getProcessedText());
/**
* Tests the combination of the align and caption filters.
*/
function testAlignAndCaptionFilters() {
$align_filter = $this->filters['filter_align'];
$caption_filter = $this->filters['filter_caption'];
$test = function($input) use ($align_filter, $caption_filter) {
return $caption_filter->process($align_filter->process($input, 'und'), 'und');
};
$attached_library = array(
'library' => array(
'filter/caption',
),
);
// Both data-caption and data-align attributes: all 3 allowed values for the
// data-align attribute.
......@@ -149,39 +220,6 @@ function testCaptionFilter() {
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Ensure the filter also works with uncommon yet valid attribute quoting.
$input = '<img src=llama.jpg data-caption=\'Loquacious llama!\' data-align=right />';
$expected = '<figure class="caption caption-img align-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Security test: attempt to inject an additional class.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center another-class-here" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Security test: attempt an XSS.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center \'onclick=\'alert(foo);" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
// Finally, ensure that this also works on any other tag.
$input = '<video src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<figure class="caption caption-video"><video src="llama.jpg"></video><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
$input = '<foobar data-caption="Loquacious llama!">baz</foobar>';
$expected = '<figure class="caption caption-foobar"><foobar>baz</foobar><figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAssets());
}
/**
......
......@@ -9,11 +9,10 @@
* - string node: The complete HTML tag whose contents are being captioned.
* - string tag: The name of the HTML tag whose contents are being captioned.
* - string caption: The caption text.
* - string|NULL align: (optional) The alignment: 'left', 'center', 'right' or
* NULL.
* - string classes: The classes of the captioned HTML tag.
*/
#}
<figure class="caption caption-{{ tag }} {%- if align %} align-{{ align }} {%- endif %}">
<figure class="caption caption-{{ tag }}{%- if classes %} {{ classes }}{%- endif %}">
{{ node }}
<figcaption>{{ caption }}</figcaption>
</figure>
......@@ -14,6 +14,12 @@ filters:
allowed_html: '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <h4> <h5> <h6> <p> <span> <img>'
filter_html_help: false
filter_html_nofollow: false
filter_align:
id: filter_align
provider: filter
status: true
weight: 7
settings: { }
filter_caption:
id: filter_caption
provider: filter
......
......@@ -5,6 +5,12 @@ weight: 1
roles:
- administrator
filters:
filter_align:
id: filter_align
provider: filter
status: true
weight: 8
settings: { }
filter_caption:
id: filter_caption
provider: filter
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment