diff --git a/core/modules/filter/config/schema/filter.schema.yml b/core/modules/filter/config/schema/filter.schema.yml index 9a70874d8a1..f3d13c7bfdc 100644 --- a/core/modules/filter/config/schema/filter.schema.yml +++ b/core/modules/filter/config/schema/filter.schema.yml @@ -46,14 +46,11 @@ filter.format.*: label: 'Dependencies' filter_settings.*: - type: sequence + type: mapping label: 'Filter settings' - sequence: - type: string - label: 'Value' filter_settings.filter_html: - type: filter + type: mapping label: 'Filter HTML' mapping: allowed_html: @@ -66,9 +63,8 @@ filter_settings.filter_html: type: boolean label: 'HTML nofollow' - filter_settings.filter_url: - type: filter + type: mapping label: 'Filter URL' mapping: filter_url_length: diff --git a/core/modules/filter/filter.post_update.php b/core/modules/filter/filter.post_update.php index 77699e25339..69509c7ffe6 100644 --- a/core/modules/filter/filter.post_update.php +++ b/core/modules/filter/filter.post_update.php @@ -7,6 +7,7 @@ use Drupal\Core\Config\Entity\ConfigEntityUpdater; use Drupal\filter\Entity\FilterFormat; +use Drupal\filter\FilterFormatInterface; /** * Sorts filter format filter configuration. @@ -19,3 +20,17 @@ function filter_post_update_sort_filters(?array &$sandbox = NULL): void { return $sorted_filters !== $filters; }); } + +/** + * Change filter_settings to type mapping. + */ +function filter_post_update_consolidate_filter_config(?array &$sandbox = NULL): void { + \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'filter_format', function (FilterFormatInterface $format): bool { + foreach ($format->get('filters') as $config) { + if (empty($config['id']) || empty($config['provider'])) { + return TRUE; + } + } + return FALSE; + }); +} diff --git a/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php index b47ed360cda..1742bcf1b43 100644 --- a/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -208,6 +208,11 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En // read and there is a minimal changeset. If the save is not trusted then // the configuration will be sorted by StorableConfigBase. ksort($this->filters); + // Ensure the filter configuration is well-formed. + array_walk($this->filters, function (array &$config, string $filter): void { + $config['id'] ??= $filter; + $config['provider'] ??= $this->filters($filter)->getPluginDefinition()['provider']; + }); } assert(is_string($this->label()), 'Filter format label is expected to be a string.'); diff --git a/core/modules/filter/src/FilterFormatFormBase.php b/core/modules/filter/src/FilterFormatFormBase.php index 7ff00b89de3..7f5d4e242a6 100644 --- a/core/modules/filter/src/FilterFormatFormBase.php +++ b/core/modules/filter/src/FilterFormatFormBase.php @@ -131,6 +131,20 @@ abstract class FilterFormatFormBase extends EntityForm { '#attributes' => ['class' => ['filter-order-weight']], ]; + // Ensure the resulting FilterFormat complies with `type: filter`. + // @see core.data_types.schema.yml + // @see \Drupal\filter\FilterFormatFormBase::submitForm() + $form['filters']['order'][$name]['id'] = [ + '#type' => 'value', + '#value' => $filter->getPluginId(), + '#parents' => ['filters', $name, 'id'], + ]; + $form['filters']['order'][$name]['provider'] = [ + '#type' => 'value', + '#value' => $filter->provider, + '#parents' => ['filters', $name, 'provider'], + ]; + // Retrieve the settings form of the filter plugin. The plugin should not be // aware of the text format. Therefore, it only receives a set of minimal // base properties to allow advanced implementations to work. diff --git a/core/modules/filter/tests/filter_test/config/schema/filter_test.schema.yml b/core/modules/filter/tests/filter_test/config/schema/filter_test.schema.yml index f54502c16df..51dc5e61b4d 100644 --- a/core/modules/filter/tests/filter_test/config/schema/filter_test.schema.yml +++ b/core/modules/filter/tests/filter_test/config/schema/filter_test.schema.yml @@ -1,7 +1,7 @@ # Schema for the configuration files of the Filter test module. filter_settings.filter_test_restrict_tags_and_attributes: - type: filter + type: mapping label: 'Filter to restrict HTML tags and attributes' mapping: restrictions: diff --git a/core/modules/filter/tests/fixtures/update/filter_post_update_consolidate_filter_config-3404431.php b/core/modules/filter/tests/fixtures/update/filter_post_update_consolidate_filter_config-3404431.php new file mode 100644 index 00000000000..6b73c76906f --- /dev/null +++ b/core/modules/filter/tests/fixtures/update/filter_post_update_consolidate_filter_config-3404431.php @@ -0,0 +1,32 @@ +select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'filter.format.plain_text') + ->execute() + ->fetchField()); + +unset($format['filters']['filter_autop']['id']); +unset($format['filters']['filter_html_escape']['provider']); +unset($format['filters']['filter_url']['id']); +unset($format['filters']['filter_url']['provider']); + +$db->update('config') + ->fields(['data' => serialize($format)]) + ->condition('collection', '') + ->condition('name', 'filter.format.plain_text') + ->execute(); diff --git a/core/modules/filter/tests/src/Functional/FilterFormatConsolidateFilterConfigUpdateTest.php b/core/modules/filter/tests/src/Functional/FilterFormatConsolidateFilterConfigUpdateTest.php new file mode 100644 index 00000000000..23e279aa029 --- /dev/null +++ b/core/modules/filter/tests/src/Functional/FilterFormatConsolidateFilterConfigUpdateTest.php @@ -0,0 +1,50 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz', + __DIR__ . '/../../fixtures/update/filter_post_update_consolidate_filter_config-3404431.php', + ]; + } + + /** + * @covers \filter_post_update_consolidate_filter_config + */ + public function testConsolidateFilterConfig() { + $format = $this->config('filter.format.plain_text'); + $this->assertArrayNotHasKey('id', $format->get('filters.filter_autop')); + $this->assertSame('filter', $format->get('filters.filter_autop.provider')); + $this->assertSame('filter_html_escape', $format->get('filters.filter_html_escape.id')); + $this->assertArrayNotHasKey('provider', $format->get('filters.filter_html_escape')); + $this->assertArrayNotHasKey('id', $format->get('filters.filter_url')); + $this->assertArrayNotHasKey('provider', $format->get('filters.filter_url')); + + $this->runUpdates(); + + $format = $this->config('filter.format.plain_text'); + $this->assertSame('filter_autop', $format->get('filters.filter_autop.id')); + $this->assertSame('filter', $format->get('filters.filter_autop.provider')); + $this->assertSame('filter_html_escape', $format->get('filters.filter_html_escape.id')); + $this->assertSame('filter', $format->get('filters.filter_html_escape.provider')); + $this->assertSame('filter_url', $format->get('filters.filter_url.id')); + $this->assertSame('filter', $format->get('filters.filter_url.provider')); + } + +} diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml index 9c804c620e1..1e1bfb5ad3f 100644 --- a/core/modules/media/config/schema/media.schema.yml +++ b/core/modules/media/config/schema/media.schema.yml @@ -119,7 +119,7 @@ media.source.field_aware: label: 'Source field' filter_settings.media_embed: - type: filter + type: mapping label: 'Media Embed' mapping: default_view_mode: