Issue #1423244 by tstoeckler, floretan, Wim Leers, lokapujya, xjm, webflo: #allowed_formats property for #type 'text_format' to filter available formats.

8.0.x
Alex Pott 2014-06-11 09:30:01 +01:00
parent e0b25fcb4b
commit 7922db66c3
8 changed files with 491 additions and 62 deletions

View File

@ -496,8 +496,11 @@ function check_markup($text, $format_id = NULL, $langcode = '', $filter_types_to
* The form element to process. Properties used:
* - #base_type: The form element #type to use for the 'value' element.
* 'textarea' by default.
* - #format: (optional) The text format ID to preselect. If NULL or not set,
* the default format for the current user will be used.
* - #format: (optional) The text format ID to preselect. If omitted, the
* default format for the current user will be used.
* - #allowed_formats: (optional) An array of text format IDs that are
* available for this element. If omitted, all text formats that the current
* user has access to will be allowed.
*
* @return
* The expanded element.
@ -536,6 +539,8 @@ function filter_process_format($element) {
$element['value']['#type'] = $element['#base_type'];
$element['value'] += element_info($element['#base_type']);
// Make sure the #default_value key is set, so we can use it below.
$element['value'] += array('#default_value' => '');
// Turn original element into a text format wrapper.
$element['#attached']['library'][] = 'filter/drupal.filter';
@ -549,15 +554,30 @@ function filter_process_format($element) {
// Get a list of formats that the current user has access to.
$formats = filter_formats($user);
// Use the default format for this user if none was selected.
if (!isset($element['#format'])) {
$element['#format'] = filter_default_format($user);
// Allow the list of formats to be restricted.
if (isset($element['#allowed_formats'])) {
// We do not add the fallback format here to allow the use-case of forcing
// certain text formats to be used for certain text areas. In case the
// fallback format is supposed to be allowed as well, it must be added to
// $element['#allowed_formats'] explicitly.
$formats = array_intersect_key($formats, array_flip($element['#allowed_formats']));
}
// If multiple text formats are available, remove the fallback. The
// "always_show_fallback_choice" is a hidden variable that has no UI. It
// defaults to false.
if (!\Drupal::config('filter.settings')->get('always_show_fallback_choice')) {
if (!isset($element['#format']) && !empty($formats)) {
// If no text format was selected, use the allowed format with the highest
// weight. This is equivalent to calling filter_default_format().
$element['#format'] = reset($formats)->format;
}
// If #allowed_formats is set, the list of formats must not be modified in any
// way. Otherwise, however, if all of the following conditions are true,
// remove the fallback format from the list of formats:
// 1. The 'always_show_fallback_choice' filter setting has not been activated.
// 2. Multiple text formats are available.
// 3. The fallback format is not the default format.
// The 'always_show_fallback_choice' filter setting is a hidden setting that
// has no UI. It defaults to FALSE.
if (!isset($element['#allowed_formats']) && !\Drupal::config('filter.settings')->get('always_show_fallback_choice')) {
$fallback_format = filter_fallback_format();
if ($element['#format'] !== $fallback_format && count($formats) > 1) {
unset($formats[$fallback_format]);
@ -599,12 +619,13 @@ function filter_process_format($element) {
$all_formats = filter_formats();
$format_exists = isset($all_formats[$element['#format']]);
$format_allowed = !isset($element['#allowed_formats']) || in_array($element['#format'], $element['#allowed_formats']);
$user_has_access = isset($formats[$element['#format']]);
$user_is_admin = user_access('administer filters');
// If the stored format does not exist, administrators have to assign a new
// format.
if (!$format_exists && $user_is_admin) {
// If the stored format does not exist or if it is not among the allowed
// formats for this textarea, administrators have to assign a new format.
if ((!$format_exists || !$format_allowed) && $user_is_admin) {
$element['format']['format']['#required'] = TRUE;
$element['format']['format']['#default_value'] = NULL;
// Force access to the format selector (it may have been denied above if

View File

@ -24,7 +24,7 @@ class FilterAPITest extends EntityUnitTestBase {
public static function getInfo() {
return array(
'name' => 'API',
'name' => 'Filter API',
'description' => 'Test the behavior of the API of the Filter module.',
'group' => 'Filter',
);
@ -34,36 +34,6 @@ class FilterAPITest extends EntityUnitTestBase {
parent::setUp();
$this->installConfig(array('system', 'filter'));
// Create Filtered HTML format.
$filtered_html_format = entity_create('filter_format', array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'filters' => array(
// Note that the filter_html filter is of the type FilterInterface::TYPE_MARKUP_LANGUAGE.
'filter_url' => array(
'weight' => -1,
'status' => 1,
),
// Note that the filter_html filter is of the type FilterInterface::TYPE_HTML_RESTRICTOR.
'filter_html' => array(
'status' => 1,
'settings' => array(
'allowed_html' => '<p> <br> <strong> <a>',
),
),
)
));
$filtered_html_format->save();
// Create Full HTML format.
$full_html_format = entity_create('filter_format', array(
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 1,
'filters' => array(),
));
$full_html_format->save();
}
/**
@ -105,13 +75,17 @@ class FilterAPITest extends EntityUnitTestBase {
$expected_filtered_text = "Text with evil content and a URL: <a href=\"http://drupal.org\">http://drupal.org</a>!";
$expected_filter_text_without_html_generators = "Text with evil content and a URL: http://drupal.org!";
$actual_filtered_text = check_markup($text, 'filtered_html', '', array());
$this->verbose("Actual:<pre>$actual_filtered_text</pre>Expected:<pre>$expected_filtered_text</pre>");
$this->assertIdentical(
check_markup($text, 'filtered_html', '', array()),
$actual_filtered_text,
$expected_filtered_text,
'Expected filter result.'
);
$actual_filtered_text_without_html_generators = check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_MARKUP_LANGUAGE));
$this->verbose("Actual:<pre>$actual_filtered_text_without_html_generators</pre>Expected:<pre>$expected_filter_text_without_html_generators</pre>");
$this->assertIdentical(
check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_MARKUP_LANGUAGE)),
$actual_filtered_text_without_html_generators,
$expected_filter_text_without_html_generators,
'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters.'
);
@ -119,8 +93,10 @@ class FilterAPITest extends EntityUnitTestBase {
// this check focuses on the ability to filter multiple filter types at once.
// Drupal core only ships with these two types of filters, so this is the
// most extensive test possible.
$actual_filtered_text_without_html_generators = check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR, FilterInterface::TYPE_MARKUP_LANGUAGE));
$this->verbose("Actual:<pre>$actual_filtered_text_without_html_generators</pre>Expected:<pre>$expected_filter_text_without_html_generators</pre>");
$this->assertIdentical(
check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR, FilterInterface::TYPE_MARKUP_LANGUAGE)),
$actual_filtered_text_without_html_generators,
$expected_filter_text_without_html_generators,
'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.'
);

View File

@ -0,0 +1,302 @@
<?php
/**
* @file
* Contains \Drupal\filter\Tests\FilterFormTest.
*/
namespace Drupal\filter\Tests;
use Drupal\Component\Utility\String;
use Drupal\simpletest\WebTestBase;
/**
* Tests form elements provided by Filter module.
*/
class FilterFormTest extends WebTestBase {
/**
* Modules to enable for this test.
*
* @var array
*/
protected static $modules = array('filter', 'filter_test');
/**
* An administrative user account that can administer text formats.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* An basic user account that can only access basic HTML text format.
*
* @var \Drupal\user\Entity\User
*/
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Text format form element',
'description' => 'Tests form elements with associated text formats.',
'group' => 'Filter',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
/** @var \Drupal\filter\FilterFormatInterface $filter_test_format */
$filter_test_format = entity_load('filter_format', 'filter_test');
/** @var \Drupal\filter\FilterFormatInterface $filtered_html_format */
$filtered_html_format = entity_load('filter_format', 'filtered_html');
/** @var \Drupal\filter\FilterFormatInterface $full_html_format */
$full_html_format = entity_load('filter_format', 'full_html');
// Create users.
$this->adminUser = $this->drupalCreateUser(array(
'administer filters',
$filtered_html_format->getPermissionName(),
$full_html_format->getPermissionName(),
$filter_test_format->getPermissionName(),
));
$this->webUser = $this->drupalCreateUser(array(
$filtered_html_format->getPermissionName(),
$filter_test_format->getPermissionName(),
));
}
/**
* Tests various different configurations of the 'text_format' element.
*/
public function testFilterForm() {
$this->doFilterFormTestAsAdmin();
$this->doFilterFormTestAsNonAdmin();
}
/**
* Tests the behavior of the 'text_format' element as an administrator.
*/
protected function doFilterFormTestAsAdmin() {
$this->drupalLogin($this->adminUser);
$this->drupalGet('filter-test/text-format');
// Test a text format element with all formats.
$formats = array('filtered_html', 'full_html', 'filter_test');
$this->assertEnabledTextarea('edit-all-formats-no-default-value');
// If no default is given, the format with the lowest weight becomes the
// default.
$this->assertOptions('edit-all-formats-no-default-format--2', $formats, 'filtered_html');
$this->assertEnabledTextarea('edit-all-formats-default-value');
// \Drupal\filter_test\Form\FilterTestFormatForm::buildForm() uses
// 'filter_test' as the default value in this case.
$this->assertOptions('edit-all-formats-default-format--2', $formats, 'filter_test');
$this->assertEnabledTextarea('edit-all-formats-default-missing-value');
// If a missing format is set as the default, administrators must select a
// valid replacement format.
$this->assertRequiredSelectAndOptions('edit-all-formats-default-missing-format--2', $formats);
// Test a text format element with a predefined list of formats.
$formats = array('full_html', 'filter_test');
$this->assertEnabledTextarea('edit-restricted-formats-no-default-value');
$this->assertOptions('edit-restricted-formats-no-default-format--2', $formats, 'full_html');
$this->assertEnabledTextarea('edit-restricted-formats-default-value');
$this->assertOptions('edit-restricted-formats-default-format--2', $formats, 'full_html');
$this->assertEnabledTextarea('edit-restricted-formats-default-missing-value');
$this->assertRequiredSelectAndOptions('edit-restricted-formats-default-missing-format--2', $formats);
$this->assertEnabledTextarea('edit-restricted-formats-default-disallowed-value');
$this->assertRequiredSelectAndOptions('edit-restricted-formats-default-disallowed-format--2', $formats);
// Test a text format element with a fixed format.
$formats = array('filter_test');
// When there is only a single option there is no point in choosing.
$this->assertEnabledTextarea('edit-single-format-no-default-value');
$this->assertNoSelect('edit-single-format-no-default-format--2');
$this->assertEnabledTextarea('edit-single-format-default-value');
$this->assertNoSelect('edit-single-format-default-format--2');
// If the select has a missing or disallowed format, administrators must
// explicitly choose the format.
$this->assertEnabledTextarea('edit-single-format-default-missing-value');
$this->assertRequiredSelectAndOptions('edit-single-format-default-missing-format--2', $formats);
$this->assertEnabledTextarea('edit-single-format-default-disallowed-value');
$this->assertRequiredSelectAndOptions('edit-single-format-default-disallowed-format--2', $formats);
}
/**
* Tests the behavior of the 'text_format' element as a normal user.
*/
protected function doFilterFormTestAsNonAdmin() {
$this->drupalLogin($this->webUser);
$this->drupalGet('filter-test/text-format');
// Test a text format element with all formats. Only formats the user has
// access to are shown.
$formats = array('filtered_html', 'filter_test');
$this->assertEnabledTextarea('edit-all-formats-no-default-value');
// If no default is given, the format with the lowest weight becomes the
// default. This happens to be 'filtered_html'.
$this->assertOptions('edit-all-formats-no-default-format--2', $formats, 'filtered_html');
$this->assertEnabledTextarea('edit-all-formats-default-value');
// \Drupal\filter_test\Form\FilterTestFormatForm::buildForm() uses
// 'filter_test' as the default value in this case.
$this->assertOptions('edit-all-formats-default-format--2', $formats, 'filter_test');
// If a missing format is given as default, non-admin users are presented
// with a disabled textarea.
$this->assertDisabledTextarea('edit-all-formats-default-missing-value');
// Test a text format element with a predefined list of formats.
$this->assertEnabledTextarea('edit-restricted-formats-no-default-value');
// The user only has access to the 'filter_test' format, so when no default
// is given that is preselected and the text format select is hidden.
$this->assertNoSelect('edit-restricted-formats-no-default-format--2');
// When the format that the user does not have access to is preselected, the
// textarea should be disabled.
$this->assertDisabledTextarea('edit-restricted-formats-default-value');
$this->assertDisabledTextarea('edit-restricted-formats-default-missing-value');
$this->assertDisabledTextarea('edit-restricted-formats-default-disallowed-value');
// Test a text format element with a fixed format.
// When there is only a single option there is no point in choosing.
$this->assertEnabledTextarea('edit-single-format-no-default-value');
$this->assertNoSelect('edit-single-format-no-default-format--2');
$this->assertEnabledTextarea('edit-single-format-default-value');
$this->assertNoSelect('edit-single-format-default-format--2');
// If the select has a missing or disallowed format make sure the textarea
// is disabled.
$this->assertDisabledTextarea('edit-single-format-default-missing-value');
$this->assertDisabledTextarea('edit-single-format-default-disallowed-value');
}
/**
* Makes sure that no select element with the given ID exists on the page.
*
* @param string $id
* The HTML ID of the select element.
*/
protected function assertNoSelect($id) {
$select = $this->xpath('//select[@id=:id]', array(':id' => $id));
$this->assertFalse($select, String::format('Field @id does not exist.', array(
'@id' => $id,
)));
return $select;
}
/**
* Asserts that a select element has the correct options.
*
* @param string $id
* The HTML ID of the select element.
* @param array $expected_options
* An array of option values.
* @param string $selected
* The value of the selected option.
*/
protected function assertOptions($id, array $expected_options, $selected) {
$select = $this->xpath('//select[@id=:id]', array(':id' => $id));
$select = reset($select);
$this->assertTrue($select instanceof \SimpleXMLElement, String::format('Field @id exists.', array(
'@id' => $id,
)));
$found_options = $this->getAllOptions($select);
foreach ($found_options as $found_key => $found_option) {
$expected_key = array_search($found_option->attributes()->value, $expected_options);
if ($expected_key !== FALSE) {
$this->pass(String::format('Option @option for field @id exists.', array(
'@option' => $expected_options[$expected_key],
'@id' => $id,
)));
unset($found_options[$found_key]);
unset($expected_options[$expected_key]);
}
}
// Make sure that all expected options were found and that there are no
// unexpected options.
foreach ($expected_options as $expected_option) {
$this->fail(String::format('Option @option for field @id exists.', array(
'@option' => $expected_option,
'@id' => $id,
)));
}
foreach ($found_options as $found_option) {
$this->fail(String::format('Option @option for field @id does not exist.', array(
'@option' => $found_option->attributes()->value,
'@id' => $id,
)));
}
$this->assertOptionSelected($id, $selected);
}
/**
* Asserts that there is a select element with the given ID that is required.
*
* @param string $id
* The HTML ID of the select element.
* @param array $options
* An array of option values that are contained in the select element
* besides the "- Select -" option.
*/
protected function assertRequiredSelectAndOptions($id, array $options) {
$select = $this->xpath('//select[@id=:id and contains(@required, "required")]', array(
':id' => $id,
));
$select = reset($select);
$this->assertTrue($select instanceof \SimpleXMLElement, String::format('Required field @id exists.', array(
'@id' => $id,
)));
// A required select element has a "- Select -" option whose key is an empty
// string.
$options[] = '';
$this->assertOptions($id, $options, '');
}
/**
* Asserts that a textarea with a given ID exists and is not disabled.
*
* @param string $id
* The HTML ID of the textarea.
*/
protected function assertEnabledTextarea($id) {
$textarea = $this->xpath('//textarea[@id=:id and not(contains(@disabled, "disabled"))]', array(
':id' => $id,
));
$textarea = reset($textarea);
$this->assertTrue($textarea instanceof \SimpleXMLElement, String::format('Enabled field @id exists.', array(
'@id' => $id,
)));
}
/**
* Asserts that a textarea with a given ID has been disabled from editing.
*
* @param string $id
* The HTML ID of the textarea.
*/
protected function assertDisabledTextarea($id) {
$textarea = $this->xpath('//textarea[@id=:id and contains(@disabled, "disabled")]', array(
':id' => $id,
));
$textarea = reset($textarea);
$this->assertTrue($textarea instanceof \SimpleXMLElement, String::format('Disabled field @id exists.', array(
'@id' => $id,
)));
$expected = 'This field has been disabled because you do not have sufficient permissions to edit it.';
$this->assertEqual((string) $textarea, $expected, String::format('Disabled textarea @id hides text in an inaccessible text format.', array(
'@id' => $id,
)));
// Make sure the text format select is not shown.
$select_id = str_replace('value', 'format--2', $id);
$this->assertNoSelect($select_id);
}
}

View File

@ -31,7 +31,7 @@ class FilterSecurityTest extends WebTestBase {
public static function getInfo() {
return array(
'name' => 'Security',
'name' => 'Filter security',
'description' => 'Test the behavior of check_markup() when a filter or text format vanishes, or when check_markup() is called in such a way that it is instructed to skip all filters of the "FilterInterface::TYPE_HTML_RESTRICTOR" type.',
'group' => 'Filter',
);
@ -43,19 +43,8 @@ class FilterSecurityTest extends WebTestBase {
// Create Basic page node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Create Filtered HTML format.
$filtered_html_format = entity_create('filter_format', array(
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'filters' => array(
// Note that the filter_html filter is of the type FilterInterface::TYPE_HTML_RESTRICTOR.
'filter_html' => array(
'status' => 1,
),
)
));
$filtered_html_format->save();
/** @var \Drupal\filter\Entity\FilterFormat $filtered_html_format */
$filtered_html_format = entity_load('filter_format', 'filtered_html');
$filtered_html_permission = $filtered_html_format->getPermissionName();
user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission));
@ -100,8 +89,8 @@ class FilterSecurityTest extends WebTestBase {
* Tests that security filters are enforced even when marked to be skipped.
*/
function testSkipSecurityFilters() {
$text = "Text with some disallowed tags: <script />, <em><object>unicorn</object></em>, <i><table></i>.";
$expected_filtered_text = "Text with some disallowed tags: , <em>unicorn</em>, .";
$text = "Text with some disallowed tags: <script />, <p><object>unicorn</object></p>, <i><table></i>.";
$expected_filtered_text = "Text with some disallowed tags: , <p>unicorn</p>, .";
$this->assertEqual(check_markup($text, 'filtered_html', '', array()), $expected_filtered_text, 'Expected filter result.');
$this->assertEqual(check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR)), $expected_filtered_text, 'Expected filter result, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.');
}

View File

@ -0,0 +1,15 @@
format: filtered_html
name: 'Filtered HTML'
weight: 1
filters:
filter_url:
id: filter_url
provider: filter
weight: -1
status: true
filter_html:
id: filter_html
provider: filter
status: true
settings:
allowed_html: '<p> <br> <strong> <a>'

View File

@ -0,0 +1,3 @@
format: full_html
name: 'Full HTML'
weight: 2

View File

@ -0,0 +1,8 @@
filter_text.text_format:
path: '/filter-test/text-format'
defaults:
_title: 'Text format test forms'
_form: 'Drupal\filter_test\Form\FilterTestFormatForm'
requirements:
_access: 'TRUE'

View File

@ -0,0 +1,115 @@
<?php
/**
* @file
* Contains \Drupal\filter_test\Form\FilterTestFormatForm.
*/
namespace Drupal\filter_test\Form;
use Drupal\Core\Form\FormBase;
/**
* Shows a test form for testing the 'text_format' form element.
*/
class FilterTestFormatForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'filter_test_format_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state) {
// This ensures that the parent array key makes it into the HTML ID of the
// form elements.
$form['#tree'] = TRUE;
$form['all_formats'] = array(
'#type' => 'details',
'#title' => 'All text formats',
);
$form['all_formats']['no_default'] = array(
'#type' => 'text_format',
'#title' => 'No default value',
);
$form['all_formats']['default'] = array(
'#type' => 'text_format',
'#title' => 'Default value',
'#format' => 'filter_test',
);
$form['all_formats']['default_missing'] = array(
'#type' => 'text_format',
'#title' => 'Missing default value',
'#format' => 'missing_format',
);
$form['restricted_formats'] = array(
'#type' => 'details',
'#title' => 'Restricted text format list',
);
$form['restricted_formats']['no_default'] = array(
'#type' => 'text_format',
'#title' => 'No default value',
'#allowed_formats' => array('full_html', 'filter_test'),
);
$form['restricted_formats']['default'] = array(
'#type' => 'text_format',
'#title' => 'Default value',
'#format' => 'full_html',
'#allowed_formats' => array('full_html', 'filter_test'),
);
$form['restricted_formats']['default_missing'] = array(
'#type' => 'text_format',
'#title' => 'Missing default value',
'#format' => 'missing_format',
'#allowed_formats' => array('full_html', 'filter_test'),
);
$form['restricted_formats']['default_disallowed'] = array(
'#type' => 'text_format',
'#title' => 'Disallowed default value',
'#format' => 'filtered_html',
'#allowed_formats' => array('full_html', 'filter_test'),
);
$form['single_format'] = array(
'#type' => 'details',
'#title' => 'Single text format',
);
$form['single_format']['no_default'] = array(
'#type' => 'text_format',
'#title' => 'No default value',
'#allowed_formats' => array('filter_test'),
);
$form['single_format']['default'] = array(
'#type' => 'text_format',
'#title' => 'Default value',
'#format' => 'filter_test',
'#allowed_formats' => array('filter_test'),
);
$form['single_format']['default_missing'] = array(
'#type' => 'text_format',
'#title' => 'Missing default value',
'#format' => 'missing_format',
'#allowed_formats' => array('filter_test'),
);
$form['single_format']['default_disallowed'] = array(
'#type' => 'text_format',
'#title' => 'Disallowed default value',
'#format' => 'full_html',
'#allowed_formats' => array('filter_test'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
}
}