diff --git a/core/modules/filter/config/schema/filter.schema.yml b/core/modules/filter/config/schema/filter.schema.yml index 9a70874d8a10976aa6875763ee65bed2d6978173..f3d13c7bfdc989cceda56f082fe8ade5b13458a3 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 77699e25339eb981a4aee892b36f54b3a4196429..69509c7ffe629efdc16a2a4f36799945503e3d0f 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 b47ed360cda14f1d5a2f5b9afc36584af275d6df..1742bcf1b43b5ebe299742c3a1fcf5b2922235b4 100644 --- a/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -208,6 +208,11 @@ public function preSave(EntityStorageInterface $storage) { // 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 7ff00b89de3315c6c7a1502977158ce300de45bd..7f5d4e242a6caf54ee7a9f3e695d4f8c141b431b 100644 --- a/core/modules/filter/src/FilterFormatFormBase.php +++ b/core/modules/filter/src/FilterFormatFormBase.php @@ -131,6 +131,20 @@ public function form(array $form, FormStateInterface $form_state) { '#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 f54502c16df80e78e3517bab24f35e09a7260f3b..51dc5e61b4d81f0ab15ba0dbf95f75844b046e6d 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 0000000000000000000000000000000000000000..6b73c76906f854cb4872de9dc2871bcaaf811b67 --- /dev/null +++ b/core/modules/filter/tests/fixtures/update/filter_post_update_consolidate_filter_config-3404431.php @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Fixture file to test filter_post_update_consolidate_filter_config(). + * + * @see https://www.drupal.org/project/drupal/issues/3404431 + * @see \Drupal\Tests\filter\Functional\FilterFormatConsolidateFilterConfigUpdateTest + * @see filter_post_update_consolidate_filter_config() + */ + +use Drupal\Core\Database\Database; + +$db = Database::getConnection(); + +$format = unserialize($db->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 0000000000000000000000000000000000000000..23e279aa029e8ef9bfe9a6086516f08c5c9827bb --- /dev/null +++ b/core/modules/filter/tests/src/Functional/FilterFormatConsolidateFilterConfigUpdateTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\filter\Functional; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + +/** + * Tests the upgrade path for filter formats. + * + * @see filter_post_update_consolidate_filter_config() + * + * @group Update + * @group legacy + */ +class FilterFormatConsolidateFilterConfigUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles(): void { + $this->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 9c804c620e1ee6e42ffd70070b7be815a974283e..1e1bfb5ad3fd442665d05980514ae0949a6a7263 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: