Commit 81e3e038 authored by alexpott's avatar alexpott

Issue #2184315 by Wim Leers: Move filter_get_filter_types_by_format() and...

Issue #2184315 by Wim Leers: Move filter_get_filter_types_by_format() and filter_get_html_restrictions_by_format() onto FilterFormatInterface.
parent 50901f95
......@@ -285,7 +285,8 @@ protected function generateFormatTagsSetting(Editor $editor) {
*/
protected function generateAllowedContentSetting(Editor $editor) {
// When nothing is disallowed, set allowedContent to true.
$filter_types = filter_get_filter_types_by_format($editor->format);
$format = entity_load('filter_format', $editor->format);
$filter_types = $format->getFilterTypes();
if (!in_array(FILTER_TYPE_HTML_RESTRICTOR, $filter_types)) {
return TRUE;
}
......@@ -303,7 +304,7 @@ protected function generateAllowedContentSetting(Editor $editor) {
}
};
$html_restrictions = filter_get_html_restrictions_by_format($editor->format);
$html_restrictions = $format->getHtmlRestrictions();
// When all HTML is allowed, also set allowedContent to true.
if ($html_restrictions === FALSE) {
return TRUE;
......
......@@ -60,7 +60,8 @@ function getMetadata(FieldItemListInterface $items) {
* Returns whether the text format has transformation filters.
*/
protected function textFormatHasTransformationFilters($format_id) {
return (bool) count(array_intersect(array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE), filter_get_filter_types_by_format($format_id)));
$format = entity_load('filter_format', $format_id);
return (bool) count(array_intersect(array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE), $format->getFiltertypes()));
}
/**
......
......@@ -328,180 +328,6 @@ function filter_default_format(AccountInterface $account = NULL) {
return $format->format;
}
/**
* Retrieves all filter types that are used in a given text format.
*
* @param string $format_id
* A text format ID.
*
* @return array
* All filter types used by filters of a given text format.
*
* @throws Exception
*/
function filter_get_filter_types_by_format($format_id) {
$filter_types = array();
if ($format_id) {
$filters = entity_load('filter_format', $format_id)->filters();
foreach ($filters as $filter) {
if ($filter->status) {
$filter_types[] = $filter->getType();
}
}
}
return array_unique($filter_types);
}
/**
* Retrieve all HTML restrictions (tags and attributes) for a given text format.
*
* Note that restrictions applied to the "*" tag (the wildcard tag, i.e. all
* tags) are treated just like any other HTML tag. That means that any
* restrictions applied to it are not automatically applied to all other tags.
* It is up to the caller to handle this in whatever way it sees fit; this way
* no information granularity is lost.
*
* @param string $format_id
* A text format ID.
*
* @return array|FALSE
* An structured array as returned by FilterInterface::getHTMLRestrictions(),
* but with the intersection of all filters in this text format.
* Will either indicate blacklisting of tags or whitelisting of tags. In the
* latter case, it's possible that restrictions on attributes are also stored.
* FALSE means there are no HTML restrictions.
*/
function filter_get_html_restrictions_by_format($format_id) {
$format = entity_load('filter_format', $format_id);
// Ignore filters that are disabled or don't have HTML restrictions.
$filters = array_filter($format->filters()->getAll(), function($filter) {
if (!$filter->status) {
return FALSE;
}
if ($filter->getType() === FILTER_TYPE_HTML_RESTRICTOR && $filter->getHTMLRestrictions() !== FALSE) {
return TRUE;
}
return FALSE;
});
if (empty($filters)) {
return FALSE;
}
else {
// From the set of remaining filters (they were filtered by array_filter()
// above), collect the list of tags and attributes that are allowed by all
// filters, i.e. the intersection of all allowed tags and attributes.
$restrictions = array_reduce($filters, function($restrictions, $filter) {
$new_restrictions = $filter->getHTMLRestrictions();
// The first filter with HTML restrictions provides the initial set.
if (!isset($restrictions)) {
return $new_restrictions;
}
// Subsequent filters with an "allowed html" setting must be intersected
// with the existing set, to ensure we only end up with the tags that are
// allowed by *all* filters with an "allowed html" setting.
else {
// Track the union of forbidden (blacklisted) tags.
if (isset($new_restrictions['forbidden_tags'])) {
if (!isset($restrictions['forbidden_tags'])) {
$restrictions['forbidden_tags'] = $new_restrictions['forbidden_tags'];
}
else {
$restrictions['forbidden_tags'] = array_unique(array_merge($restrictions['forbidden_tags'], $new_restrictions['forbidden_tags']));
}
}
// Track the intersection of allowed (whitelisted) tags.
if (isset($restrictions['allowed'])) {
$intersection = $restrictions['allowed'];
foreach ($intersection as $tag => $attributes) {
// If the current tag is not whitelisted by the new filter, then
// it's outside of the intersection.
if (!array_key_exists($tag, $new_restrictions['allowed'])) {
// The exception is the asterisk (which applies to all tags): it
// does not need to be whitelisted by every filter in order to be
// used; not every filter needs attribute restrictions on all tags.
if ($tag === '*') {
continue;
}
unset($intersection[$tag]);
}
// The tag is in the intersection, but now we must calculate the
// intersection of the allowed attributes.
else {
$current_attributes = $intersection[$tag];
$new_attributes = $new_restrictions['allowed'][$tag];
// The current intersection does not allow any attributes, never
// allow.
if (!is_array($current_attributes) && $current_attributes == FALSE) {
continue;
}
// The new filter allows less attributes (all -> list or none).
else if (!is_array($current_attributes) && $current_attributes == TRUE && ($new_attributes == FALSE || is_array($new_attributes))) {
$intersection[$tag] = $new_attributes;
}
// The new filter allows less attributes (list -> none).
else if (is_array($current_attributes) && $new_attributes == FALSE) {
$intersection[$tag] = $new_attributes;
}
// The new filter allows more attributes; retain current.
else if (is_array($current_attributes) && $new_attributes == TRUE) {
continue;
}
// The new filter allows the same attributes; retain current.
else if ($current_attributes == $new_attributes) {
continue;
}
// Both list an array of attribute values; do an intersection,
// where we take into account that a value of:
// - TRUE means the attribute value is allowed;
// - FALSE means the attribute value is forbidden;
// hence we keep the ANDed result.
else {
$intersection[$tag] = array_intersect_key($intersection[$tag], $new_attributes);
foreach (array_keys($intersection[$tag]) as $attribute_value) {
$intersection[$tag][$attribute_value] = $intersection[$tag][$attribute_value] && $new_attributes[$attribute_value];
}
}
}
}
$restrictions['allowed'] = $intersection;
}
return $restrictions;
}
}, NULL);
// Simplification: if we have both a (intersected) whitelist and a (unioned)
// blacklist, then remove any tags from the whitelist that also exist in the
// blacklist. Now the whitelist alone expresses all tag-level restrictions,
// and we can delete the blacklist.
if (isset($restrictions['allowed']) && isset($restrictions['forbidden_tags'])) {
foreach ($restrictions['forbidden_tags'] as $tag) {
if (isset($restrictions['allowed'][$tag])) {
unset($restrictions['allowed'][$tag]);
}
}
unset($restrictions['forbidden_tags']);
}
// Simplification: if the only remaining allowed tag is the asterisk (which
// contains attribute restrictions that apply to all tags), and only
// whitelisting filters were used, then effectively nothing is allowed.
if (isset($restrictions['allowed'])) {
if (count($restrictions['allowed']) === 1 && array_key_exists('*', $restrictions['allowed']) && !isset($restrictions['forbidden_tags'])) {
$restrictions['allowed'] = array();
}
}
return $restrictions;
}
}
/**
* Returns the ID of the fallback text format that all users have access to.
*
......
......@@ -261,4 +261,150 @@ public function getPermissionName() {
return !$this->isFallbackFormat() ? 'use text format ' . $this->id() : FALSE;
}
/**
* {@inheritdoc}
*/
public function getFilterTypes() {
$filter_types = array();
$filters = $this->filters();
foreach ($filters as $filter) {
if ($filter->status) {
$filter_types[] = $filter->getType();
}
}
return array_unique($filter_types);
}
/**
* {@inheritdoc}
*/
public function getHtmlRestrictions() {
// Ignore filters that are disabled or don't have HTML restrictions.
$filters = array_filter($this->filters()->getAll(), function($filter) {
if (!$filter->status) {
return FALSE;
}
if ($filter->getType() === FILTER_TYPE_HTML_RESTRICTOR && $filter->getHTMLRestrictions() !== FALSE) {
return TRUE;
}
return FALSE;
});
if (empty($filters)) {
return FALSE;
}
else {
// From the set of remaining filters (they were filtered by array_filter()
// above), collect the list of tags and attributes that are allowed by all
// filters, i.e. the intersection of all allowed tags and attributes.
$restrictions = array_reduce($filters, function($restrictions, $filter) {
$new_restrictions = $filter->getHTMLRestrictions();
// The first filter with HTML restrictions provides the initial set.
if (!isset($restrictions)) {
return $new_restrictions;
}
// Subsequent filters with an "allowed html" setting must be intersected
// with the existing set, to ensure we only end up with the tags that are
// allowed by *all* filters with an "allowed html" setting.
else {
// Track the union of forbidden (blacklisted) tags.
if (isset($new_restrictions['forbidden_tags'])) {
if (!isset($restrictions['forbidden_tags'])) {
$restrictions['forbidden_tags'] = $new_restrictions['forbidden_tags'];
}
else {
$restrictions['forbidden_tags'] = array_unique(array_merge($restrictions['forbidden_tags'], $new_restrictions['forbidden_tags']));
}
}
// Track the intersection of allowed (whitelisted) tags.
if (isset($restrictions['allowed'])) {
$intersection = $restrictions['allowed'];
foreach ($intersection as $tag => $attributes) {
// If the current tag is not whitelisted by the new filter, then
// it's outside of the intersection.
if (!array_key_exists($tag, $new_restrictions['allowed'])) {
// The exception is the asterisk (which applies to all tags): it
// does not need to be whitelisted by every filter in order to be
// used; not every filter needs attribute restrictions on all tags.
if ($tag === '*') {
continue;
}
unset($intersection[$tag]);
}
// The tag is in the intersection, but now we must calculate the
// intersection of the allowed attributes.
else {
$current_attributes = $intersection[$tag];
$new_attributes = $new_restrictions['allowed'][$tag];
// The current intersection does not allow any attributes, never
// allow.
if (!is_array($current_attributes) && $current_attributes == FALSE) {
continue;
}
// The new filter allows less attributes (all -> list or none).
else if (!is_array($current_attributes) && $current_attributes == TRUE && ($new_attributes == FALSE || is_array($new_attributes))) {
$intersection[$tag] = $new_attributes;
}
// The new filter allows less attributes (list -> none).
else if (is_array($current_attributes) && $new_attributes == FALSE) {
$intersection[$tag] = $new_attributes;
}
// The new filter allows more attributes; retain current.
else if (is_array($current_attributes) && $new_attributes == TRUE) {
continue;
}
// The new filter allows the same attributes; retain current.
else if ($current_attributes == $new_attributes) {
continue;
}
// Both list an array of attribute values; do an intersection,
// where we take into account that a value of:
// - TRUE means the attribute value is allowed;
// - FALSE means the attribute value is forbidden;
// hence we keep the ANDed result.
else {
$intersection[$tag] = array_intersect_key($intersection[$tag], $new_attributes);
foreach (array_keys($intersection[$tag]) as $attribute_value) {
$intersection[$tag][$attribute_value] = $intersection[$tag][$attribute_value] && $new_attributes[$attribute_value];
}
}
}
}
$restrictions['allowed'] = $intersection;
}
return $restrictions;
}
}, NULL);
// Simplification: if we have both a (intersected) whitelist and a (unioned)
// blacklist, then remove any tags from the whitelist that also exist in the
// blacklist. Now the whitelist alone expresses all tag-level restrictions,
// and we can delete the blacklist.
if (isset($restrictions['allowed']) && isset($restrictions['forbidden_tags'])) {
foreach ($restrictions['forbidden_tags'] as $tag) {
if (isset($restrictions['allowed'][$tag])) {
unset($restrictions['allowed'][$tag]);
}
}
unset($restrictions['forbidden_tags']);
}
// Simplification: if the only remaining allowed tag is the asterisk (which
// contains attribute restrictions that apply to all tags), and only
// whitelisting filters were used, then effectively nothing is allowed.
if (isset($restrictions['allowed'])) {
if (count($restrictions['allowed']) === 1 && array_key_exists('*', $restrictions['allowed']) && !isset($restrictions['forbidden_tags'])) {
$restrictions['allowed'] = array();
}
}
return $restrictions;
}
}
}
......@@ -58,4 +58,30 @@ public function isFallbackFormat();
*/
public function getPermissionName();
/**
* Retrieves all filter types that are used in the text format.
*
* @return array
* All filter types used by filters of the text format.
*/
public function getFilterTypes();
/**
* Retrieve all HTML restrictions (tags and attributes) for the text format.
*
* Note that restrictions applied to the "*" tag (the wildcard tag, i.e. all
* tags) are treated just like any other HTML tag. That means that any
* restrictions applied to it are not automatically applied to all other tags.
* It is up to the caller to handle this in whatever way it sees fit; this way
* no information granularity is lost.
*
* @return array|FALSE
* A structured array as returned by FilterInterface::getHTMLRestrictions(),
* but with the intersection of all filters in this text format.
* Will either indicate blacklisting of tags or whitelisting of tags. In
* the latter case, it's possible that restrictions on attributes are also
* stored. FALSE means there are no HTML restrictions.
*/
public function getHtmlRestrictions();
}
......@@ -249,7 +249,7 @@ public function process($text, $langcode, $cache, $cache_id);
* FALSE
* @endcode
*
* @see filter_get_html_restrictions_by_format()
* @see \Drupal\filter\Entity\FilterFormatInterface::getHtmlRestrictions()
*/
public function getHTMLRestrictions();
......
......@@ -95,32 +95,34 @@ function testCheckMarkup() {
/**
* Tests the following functions for a variety of formats:
* - filter_get_html_restrictions_by_format()
* - filter_get_filter_types_by_format()
* - \Drupal\filter\Entity\FilterFormatInterface::getHtmlRestrictions()
* - \Drupal\filter\Entity\FilterFormatInterface::getFilterTypes()
*/
function testFilterFormatAPI() {
// Test on filtered_html.
$filtered_html_format = entity_load('filter_format', 'filtered_html');
$this->assertIdentical(
filter_get_html_restrictions_by_format('filtered_html'),
$filtered_html_format->getHtmlRestrictions(),
array('allowed' => array('p' => TRUE, 'br' => TRUE, 'strong' => TRUE, 'a' => TRUE, '*' => array('style' => FALSE, 'on*' => FALSE))),
'filter_get_html_restrictions_by_format() works as expected for the filtered_html format.'
'FilterFormatInterface::getHtmlRestrictions() works as expected for the filtered_html format.'
);
$this->assertIdentical(
filter_get_filter_types_by_format('filtered_html'),
$filtered_html_format->getFilterTypes(),
array(FILTER_TYPE_MARKUP_LANGUAGE, FILTER_TYPE_HTML_RESTRICTOR),
'filter_get_filter_types_by_format() works as expected for the filtered_html format.'
'FilterFormatInterface::getFilterTypes() works as expected for the filtered_html format.'
);
// Test on full_html.
$full_html_format = entity_load('filter_format', 'full_html');
$this->assertIdentical(
filter_get_html_restrictions_by_format('full_html'),
$full_html_format->getHtmlRestrictions(),
FALSE, // Every tag is allowed.
'filter_get_html_restrictions_by_format() works as expected for the full_html format.'
'FilterFormatInterface::getHtmlRestrictions() works as expected for the full_html format.'
);
$this->assertIdentical(
filter_get_filter_types_by_format('full_html'),
$full_html_format->getFilterTypes(),
array(),
'filter_get_filter_types_by_format() works as expected for the full_html format.'
'FilterFormatInterface::getFilterTypes() works as expected for the full_html format.'
);
// Test on stupid_filtered_html, where nothing is allowed.
......@@ -138,19 +140,19 @@ function testFilterFormatAPI() {
));
$stupid_filtered_html_format->save();
$this->assertIdentical(
filter_get_html_restrictions_by_format('stupid_filtered_html'),
$stupid_filtered_html_format->getHtmlRestrictions(),
array('allowed' => array()), // No tag is allowed.
'filter_get_html_restrictions_by_format() works as expected for the stupid_filtered_html format.'
'FilterFormatInterface::getHtmlRestrictions() works as expected for the stupid_filtered_html format.'
);
$this->assertIdentical(
filter_get_filter_types_by_format('stupid_filtered_html'),
$stupid_filtered_html_format->getFilterTypes(),
array(FILTER_TYPE_HTML_RESTRICTOR),
'filter_get_filter_types_by_format() works as expected for the stupid_filtered_html format.'
'FilterFormatInterface::getFilterTypes() works as expected for the stupid_filtered_html format.'
);
// Test on very_restricted_html, where there's two different filters of the
// FILTER_TYPE_HTML_RESTRICTOR type, each restricting in different ways.
$very_restricted_html = entity_create('filter_format', array(
$very_restricted_html_format = entity_create('filter_format', array(
'format' => 'very_restricted_html',
'name' => 'Very Restricted HTML',
'filters' => array(
......@@ -175,16 +177,16 @@ function testFilterFormatAPI() {
),
)
));
$very_restricted_html->save();
$very_restricted_html_format->save();
$this->assertIdentical(
filter_get_html_restrictions_by_format('very_restricted_html'),
$very_restricted_html_format->getHtmlRestrictions(),
array('allowed' => array('p' => TRUE, 'br' => FALSE, 'a' => array('href' => TRUE), '*' => array('style' => FALSE, 'on*' => FALSE))),
'filter_get_html_restrictions_by_format() works as expected for the very_restricted_html format.'
'FilterFormatInterface::getHtmlRestrictions() works as expected for the very_restricted_html format.'
);
$this->assertIdentical(
filter_get_filter_types_by_format('very_restricted_html'),
$very_restricted_html_format->getFilterTypes(),
array(FILTER_TYPE_HTML_RESTRICTOR),
'filter_get_filter_types_by_format() works as expected for the very_restricted_html format.'
'FilterFormatInterface::getFilterTypes() works as expected for the very_restricted_html format.'
);
}
......
......@@ -16,7 +16,7 @@
* @Filter(
* id = "filter_test_restrict_tags_and_attributes",
* title = @Translation("Tag and attribute restricting filter"),
* description = @Translation("Used for testing filter_get_html_restrictions_by_format()."),
* description = @Translation("Used for testing \Drupal\filter\Entity\FilterFormatInterface::getHtmlRestrictions()."),
* type = FILTER_TYPE_HTML_RESTRICTOR
* )
*/
......
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