core/modules/media/media.module | 139 ++++++++++++++++++ .../MediaFilterConfigurationUiTest.php | 159 +++++++++++++++++++++ 2 files changed, 298 insertions(+) diff --git a/core/modules/media/media.module b/core/modules/media/media.module index 48ef64777f..de22c704d8 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) { $entity_type->setLinkTemplate('canonical', '/media/{media}'); } } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_form_filter_format_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so we can ensure the order of filters + // is correct. + $form['#validate'][] = 'media_filter_format_edit_form_validate'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_form_filter_format_add_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so we can ensure the order of filters + // is correct. + $form['#validate'][] = 'media_filter_format_edit_form_validate'; +} + +/** + * Validate callback to ensure filter order and allowed_html are compatible. + */ +function media_filter_format_edit_form_validate($form, FormStateInterface $form_state) { + // This validate handler is not applicable when using the 'Configure' button. + if ($form_state->getTriggeringElement()['#name'] === 'editor_configure') { + return; + } + + $allowed_html_path = [ + 'filters', + 'filter_html', + 'settings', + 'allowed_html', + ]; + + $filter_html_settings_path = [ + 'filters', + 'filter_html', + 'settings', + ]; + + $filter_html_enabled = $form_state->getValue([ + 'filters', + 'filter_html', + 'status', + ]); + + $media_embed_enabled = $form_state->getValue([ + 'filters', + 'media_embed', + 'status', + ]); + + if (!$media_embed_enabled) { + return; + } + + $get_filter_label = function ($filter_plugin_id) use ($form) { + return (string) $form['filters']['order'][$filter_plugin_id]['filter']['#markup']; + }; + + if ($filter_html_enabled && $allowed_html = $form_state->getValue($allowed_html_path)) { + /** @var \Drupal\filter\Entity\FilterFormat $filter_format */ + $filter_format = $form_state->getFormObject()->getEntity(); + + $filter_html = $filter_format->filters()->get('filter_html'); + $filter_html->setConfiguration(['settings' => $form_state->getValue($filter_html_settings_path)]); + $restrictions = $filter_html->getHTMLRestrictions(); + $allowed = $restrictions['allowed']; + + // Require `` HTML tag if filter_html is enabled. + if (!isset($allowed['drupal-media'])) { + $form_state->setError($form['filters']['settings']['filter_html']['allowed_html'], t('The %media-embed-filter-label filter requires <drupal-media> among the allowed HTML tags.', [ + '%media-embed-filter-label' => $get_filter_label('entity_embed'), + ])); + } + else { + $required_attributes = [ + 'data-entity-type', + 'data-entity-uuid', + 'data-align', + 'data-caption', + 'alt', + 'title', + ]; + + // If there are no attributes, the allowed item is set to FALSE, + // otherwise, it is set to an array. + if ($allowed['drupal-media'] === FALSE) { + $missing_attributes = $required_attributes; + } + else { + $missing_attributes = array_diff($required_attributes, array_keys($allowed['drupal-media'])); + } + + if ($missing_attributes) { + $form_state->setError($form['filters']['settings']['filter_html']['allowed_html'], t('The <drupal-media> tag in the allowed HTML tags is missing the following attributes: %list.', [ + '%list' => implode(', ', $missing_attributes), + ])); + } + } + } + + $filters = $form_state->getValue('filters'); + + // The "media_embed" filter must run after "filter_align", "filter_caption", + // and "filter_html_image_secure". + $precedents = [ + 'filter_align', + 'filter_caption', + 'filter_html_image_secure', + ]; + + $error_filters = []; + foreach ($precedents as $filter_name) { + // A filter that should run before media embed filter. + $precedent = $filters[$filter_name]; + + if (empty($precedent['status']) || !isset($precedent['weight'])) { + continue; + } + + if ($precedent['weight'] >= $filters['media_embed']['weight']) { + $error_filters[$filter_name] = $get_filter_label($filter_name); + } + } + + if (!empty($error_filters)) { + $singular = 'The %media-embed-filter-label filter needs to be placed after the %filter filter.'; + $plural = 'The %media-embed-filter-label filter needs to be placed after the following filters: %filters.'; + $error_message = \Drupal::translation()->formatPlural(count($error_filters), $singular, $plural, [ + '%media-embed-filter-label' => $get_filter_label('entity_embed'), + '%filter' => reset($error_filters), + '%filters' => implode(', ', $error_filters), + ]); + + $form_state->setErrorByName('filters', $error_message); + } +} diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaFilterConfigurationUiTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaFilterConfigurationUiTest.php new file mode 100644 index 0000000000..e5d79d062b --- /dev/null +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaFilterConfigurationUiTest.php @@ -0,0 +1,159 @@ + 'media_embed_test', + 'name' => 'Test format', + 'filters' => [], + ]); + $format->save(); + + $this->drupalLogin($this->drupalCreateUser([ + 'administer filters', + $format->getPermissionName(), + ])); + } + + /** + * @covers ::media_form_filter_format_add_form_alter + * @covers ::media_filter_format_edit_form_validate + * @dataProvider providerTestValidations + */ + public function testValidationWhenAdding($filter_html_status, $media_embed_status, $allowed_html, $expected_error_message) { + $this->drupalGet('admin/config/content/formats/add'); + + // Enable the `filter_html` and `media_embed` filters. + $page = $this->getSession()->getPage(); + $page->fillField('name', 'Another test format'); + $this->showHiddenFields(); + $page->findField('format')->setValue('another_media_embed_test'); + if ($filter_html_status) { + $page->checkField('filters[filter_html][status]'); + } + if ($media_embed_status) { + $page->checkField('filters[media_embed][status]'); + } + if (!empty($allowed_html)) { + $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html); + } + $this->assertSession()->buttonExists('Save configuration')->press(); + + if ($expected_error_message) { + $this->assertSession()->pageTextNotContains('Added text format Another test format.'); + $this->assertSession()->pageTextContains($expected_error_message); + } + else { + $this->assertSession()->pageTextContains('Added text format Another test format.'); + } + } + + /** + * @covers ::media_form_filter_format_edit_form_alter + * @covers ::media_filter_format_edit_form_validate + * @dataProvider providerTestValidations + */ + public function testValidationWhenEditing($filter_html_status, $media_embed_status, $allowed_html, $expected_error_message) { + $this->drupalGet('admin/config/content/formats/manage/media_embed_test'); + + // Enable the `filter_html` and `media_embed` filters. + $page = $this->getSession()->getPage(); + if ($filter_html_status) { + $page->checkField('filters[filter_html][status]'); + } + if ($media_embed_status) { + $page->checkField('filters[media_embed][status]'); + } + if (!empty($allowed_html)) { + $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html); + } + $this->assertSession()->buttonExists('Save configuration')->press(); + + if ($expected_error_message) { + $this->assertSession()->pageTextNotContains('The text format Test format has been updated.'); + $this->assertSession()->pageTextContains($expected_error_message); + } + else { + $this->assertSession()->pageTextContains('The text format Test format has been updated.'); + } + } + + /** + * Data provider for testValidationWhenAdding() and + * testValidationWhenEditing(). + */ + public function providerTestValidations() { + return [ + 'Tests that no filter_html occurs when filter_html not enabled.' => [ + 'filters[filter_html][status]' => FALSE, + 'filters[media_embed][status]' => TRUE, + 'allowed_html' => FALSE, + 'expected_error_message' => FALSE, + ], + 'Tests validation when both filter_html and media_embed are disabled.' => [ + 'filters[filter_html][status]' => FALSE, + 'filters[media_embed][status]' => FALSE, + 'allowed_html' => FALSE, + 'expected_error_message' => FALSE, + ], + 'Tests validation when media_embed filter not enabled and filter_html is enabled.' => [ + 'filters[filter_html][status]' => TRUE, + 'filters[media_embed][status]' => FALSE, + 'allowed_html' => 'default', + 'expected_error_message' => FALSE, + ], + 'Tests validation when drupal-media element has no attributes.' => [ + 'filters[filter_html][status]' => TRUE, + 'filters[media_embed][status]' => TRUE, + 'allowed_html' => "