.../ckeditor/Plugin/ckeditor/plugin/Internal.php | 50 ++++++++- .../lib/Drupal/ckeditor/Tests/CKEditorTest.php | 21 +++- core/modules/filter/filter.api.php | 39 +++++++ core/modules/filter/filter.module | 113 +++++++++++++++++++- .../lib/Drupal/filter/Tests/FilterAPITest.php | 97 +++++++++++++++-- .../filter/tests/filter_test/filter_test.module | 15 ++- 6 files changed, 318 insertions(+), 17 deletions(-) diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php index 6c8fd54..a3e106d 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php @@ -56,7 +56,11 @@ public function getConfig(Editor $editor) { ), ); - // Next, add the format_tags setting, if its button is enabled. + // Add the allowedContent setting, which ensures CKEditor only allows tags + // and attributes that are allowed by the text format for this text editor. + $config['allowedContent'] = $this->generateAllowedContentSetting($editor); + + // Add the format_tags setting, if its button is enabled. $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons'])); if (in_array('Format', $toolbar_buttons)) { $config['format_tags'] = $this->generateFormatTagsSetting($editor); @@ -243,6 +247,7 @@ public function getButtons() { * * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor * A configured text editor object. + * * @return array * An array containing the "format_tags" configuration. */ @@ -265,4 +270,47 @@ protected function generateFormatTagsSetting(Editor $editor) { return implode(';', $format_tags); } + /** + * Builds the "allowedContent" configuration part of the CKEditor JS settings. + * + * This ensures that CKEditor obeys the HTML restrictions defined by Drupal's + * filter system, by enabling CKEditor's Advanced Content Filter (ACF) + * functionality: http://ckeditor.com/blog/CKEditor-4.1-RC-Released. + * + * @see getConfig() + * + * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor + * A configured text editor object. + * + * @return string|TRUE + * The "allowedContent" configuration: a well-formatted string or TRUE. The + * latter indicates that anything is allowed. + */ + protected function generateAllowedContentSetting(Editor $editor) { + $filter_types = filter_get_filter_types_by_format($editor->format); + + // When nothing is disallowed, set allowedContent to true. + if (!in_array(FILTER_TYPE_HTML_RESTRICTOR, $filter_types)) { + return TRUE; + } + // Generate setting that accurately reflects allowed tags and attributes. + else { + $allowed_html = filter_get_allowed_html_by_format($editor->format); + // When all HTML is allowed, also set allowedContent to true. + if ($allowed_html === TRUE) { + return TRUE; + } + $setting = array(); + foreach($allowed_html as $tag => $attributes) { + if ($attributes === TRUE) { + $setting[] = $tag . '[*]'; + } + else { + $setting[] = $tag . '[' . implode(',', $attributes) . ']'; + } + } + return implode(';', $setting); + } + } + } diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php index db4dbec..8b3277f 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php @@ -53,6 +53,9 @@ function setUp() { 'filters' => array( 'filter_html' => array( 'status' => 1, + 'settings' => array( + 'allowed_html' => '
',
+ )
),
),
));
@@ -76,6 +79,7 @@ function testGetJSSettings() {
// Default toolbar.
$expected_config = $this->getDefaultInternalConfig() + array(
+ 'allowedContent' => $this->getDefaultAllowedContentConfig(),
'toolbar' => $this->getDefaultToolbarConfig(),
'contentsCss' => $this->getDefaultContentsCssConfig(),
'extraPlugins' => '',
@@ -104,13 +108,21 @@ function testGetJSSettings() {
$expected_config['keystrokes'] = array(array(1114187, 'link'), array(1114188, NULL));
$this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
- // Change the allowed HTML tags; the "format_tags" setting for CKEditor
- // should automatically be updated as well.
+ // Change the allowed HTML tags; the "allowedContent" and format_tags"
+ // settings for CKEditor should automatically be updated as well.
$format = entity_load('filter_format', 'filtered_html');
$format->filters['filter_html']['settings']['allowed_html'] .= '
';
$format->save();
+ $expected_config['allowedContent'] = 'h4[*];h5[*];h6[*];p[*];br[*];strong[*];a[*];pre[*];h3[*]';
$expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
$this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+
+ // Remove the filtered_html filter: allow *all *tags.
+ unset($format->filters['filter_html']);
+ $format->save();
+ $expected_config['allowedContent'] = TRUE;
+ $expected_config['format_tags'] = 'p;h1;h2;h3;h4;h5;h6;pre';
+ $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
}
/**
@@ -166,6 +178,7 @@ function testInternalGetConfig() {
// Default toolbar.
$expected = $this->getDefaultInternalConfig();
+ $expected['allowedContent'] = $this->getDefaultAllowedContentConfig();
$this->assertIdentical($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for default toolbar.');
// Format dropdown/button enabled: new setting should be present.
@@ -230,6 +243,10 @@ protected function getDefaultInternalConfig() {
);
}
+ protected function getDefaultAllowedContentConfig() {
+ return 'h4[*];h5[*];h6[*];p[*];br[*];strong[*];a[*]';
+ }
+
protected function getDefaultToolbarConfig() {
return array(
0 => array('items' => array('Bold', 'Italic')),
diff --git a/core/modules/filter/filter.api.php b/core/modules/filter/filter.api.php
index 49311c8..cb929fa 100644
--- a/core/modules/filter/filter.api.php
+++ b/core/modules/filter/filter.api.php
@@ -82,6 +82,10 @@
* - tips callback: The name of a function that returns end-user-facing
* filter usage guidelines for the filter. See hook_filter_FILTER_tips()
* for details.
+ * - allowed html callback: The name of a function that returns allowed HTML
+ * tags and attributes for the filter. May be implemented by filters of the
+ * type FILTER_TYPE_HTML_RESTRICTOR. See hook_filter_FILTER_allowed_html()
+ * for details.
* - weight: A default weight for the filter in new text formats.
*
* @see filter_example.module
@@ -184,6 +188,41 @@ function hook_filter_FILTER_settings($form, &$form_state, $filter, $format, $def
}
/**
+ * Filter allowed HTML callback for hook_filter_info().
+ *
+ * Note: This is not really a hook. The function name is manually specified via
+ * 'allowed html callback' in hook_filter_info(), with this recommended callback
+ * name pattern. It is called from filter_get_allowed_html_by_format().
+ *
+ * This callback function is only necessary for filters that strip away HTML
+ * tags (and possibly attributes) and allows other modules to gain insight in a
+ * generic manner into which HTML tags and attributes are allowed by a format.
+ *
+ * @param $filter
+ * The filter object containing settings for the given format.
+ *
+ * @return array
+ * A nested array with the allowed tags as keys, and for each of those tags
+ * (keys) the corresponding allowed attributes. An empty array for allowed
+ * attributes means no attributes are allowed, TRUE means all attributes are
+ * allowed.
+ *
+ * @see filter_get_allowed_html_by_format()
+ */
+function hook_filter_FILTER_allowed_html($filter) {
+ return array(
+ // Allows no attributes on the