From f13ad693c8775472dca19fc6ef935131d5ee6919 Mon Sep 17 00:00:00 2001 From: Lauri Eskola Date: Wed, 26 Jul 2023 20:30:33 +0300 Subject: [PATCH] Issue #3361534 by Wim Leers, borisson_, longwave, catch: KernelTestBase::$strictConfigSchema = TRUE and BrowserTestBase::$strictConfigSchema = TRUE do not actually strictly validate --- core/config/schema/core.data_types.schema.yml | 5 +- .../Core/Config/Schema/SchemaCheckTrait.php | 29 +++++ .../block/config/schema/block.schema.yml | 4 +- .../block/tests/src/Functional/BlockTest.php | 1 + .../src/Kernel/BlockContentDeletionTest.php | 2 +- .../ckeditor5/src/Plugin/Editor/CKEditor5.php | 5 - .../tests/src/Functional/ImageUploadTest.php | 6 +- .../src/Kernel/CKEditor5PluginManagerTest.php | 10 ++ .../src/Kernel/SmartDefaultSettingsTest.php | 7 ++ .../tests/src/Kernel/ValidatorsTest.php | 21 ++-- .../config/install/config_test.validation.yml | 1 + .../src/Functional/ConfigImportAllTest.php | 3 + .../ImageFieldDefaultImagesTest.php | 2 +- .../config/schema/language.schema.yml | 4 + .../src/Entity/ContentLanguageSettings.php | 21 ++++ .../LanguageUILanguageNegotiationTest.php | 9 ++ .../media/config/install/media.settings.yml | 2 +- .../media/config/schema/media.schema.yml | 1 + core/modules/media/media.post_update.php | 12 ++ .../media/src/Form/MediaSettingsForm.php | 7 +- .../media/tests/fixtures/update/media.php | 18 +++ .../src/Functional/MediaSettingsTest.php | 28 ++++- ...aSettingsDefaultIframeDomainUpdateTest.php | 60 ++++++++++ .../config/schema/shortcut.schema.yml | 4 +- .../config/install/system.theme.global.yml | 2 +- .../system/config/schema/system.schema.yml | 4 +- core/modules/system/system.post_update.php | 12 ++ ...lThemeSettingsDefaultLogoUrlUpdateTest.php | 45 +++++++ .../update/config/install/update.settings.yml | 2 +- .../update/config/schema/update.schema.yml | 1 + ...pdateSettingsDefaultFetchUrlUpdateTest.php | 110 ++++++++++++++++++ core/modules/update/update.post_update.php | 12 ++ .../views/tests/src/Kernel/TestViewsTest.php | 1 + .../tests/src/Functional/AreaEntityUITest.php | 2 +- .../install/editor.editor.basic_html.yml | 5 + .../install/editor.editor.full_html.yml | 5 + .../config/install/system.theme.global.yml | 2 +- .../config/install/system.theme.global.yml | 2 +- .../KernelTests/Config/TypedConfigTest.php | 7 +- .../Config/ConfigEntityValidationTestBase.php | 8 +- .../Core/Config/DefaultConfigTest.php | 76 ------------ .../Core/Config/SchemaCheckTraitTest.php | 5 + 42 files changed, 451 insertions(+), 112 deletions(-) create mode 100644 core/modules/media/tests/src/Functional/Update/MediaSettingsDefaultIframeDomainUpdateTest.php create mode 100644 core/modules/system/tests/src/Functional/Update/GlobalThemeSettingsDefaultLogoUrlUpdateTest.php create mode 100644 core/modules/update/tests/src/Functional/Update/UpdateSettingsDefaultFetchUrlUpdateTest.php delete mode 100644 core/tests/Drupal/KernelTests/Core/Config/DefaultConfigTest.php diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 322a9514f2c4..f54e24c3ee99 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -99,7 +99,9 @@ machine_name: type: string label: 'Machine name' constraints: - Regex: '/^[a-z0-9_]+$/' + Regex: + pattern: '/^[a-z0-9_]+$/' + message: "The %value machine name is not valid." Length: # @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH max: 166 @@ -233,6 +235,7 @@ theme_settings: label: 'Logo path' url: type: uri + nullable: true label: 'URL' use_default: type: boolean diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php index 431469c7faff..44ab5ba5974c 100644 --- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php +++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php @@ -9,6 +9,7 @@ use Drupal\Core\TypedData\Type\BooleanInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\Type\FloatInterface; use Drupal\Core\TypedData\Type\IntegerInterface; +use Symfony\Component\Validator\ConstraintViolation; /** * Provides a trait for checking configuration schema. @@ -57,6 +58,34 @@ trait SchemaCheckTrait { $errors[] = $this->checkValue($key, $value); } $errors = array_merge(...$errors); + // Also perform explicit validation. Note this does NOT require every node + // in the config schema tree to have validation constraints defined. + $violations = $this->schema->validate(); + $ignored_validation_constraint_messages = [ + // @see \Drupal\Core\Config\Plugin\Validation\Constraint\ConfigExistsConstraint::$message + // @todo Remove this in https://www.drupal.org/project/drupal/issues/3362453 + "The '.*' config does not exist.", + // @see \Drupal\Core\Extension\Plugin\Validation\Constraint\ExtensionExistsConstraint::$moduleMessage + // @see \Drupal\Core\Extension\Plugin\Validation\Constraint\ExtensionExistsConstraint::$themeMessage + // @todo Remove this in https://www.drupal.org/project/drupal/issues/3362456 + "Module '.*' is not installed.", + "Theme '.*' is not installed.", + // @see \Drupal\Core\Plugin\Plugin\Validation\Constraint\PluginExistsConstraint::$unknownPluginMessage + // @todo Remove this in https://www.drupal.org/project/drupal/issues/3362457 + "The '.*' plugin does not exist.", + // @see "machine_name" in core.data_types.schema.yml + // @todo Remove this in https://www.drupal.org/project/drupal/issues/3372972 + "The .*<\/em> machine name is not valid.", + ]; + $filtered_violations = array_filter( + iterator_to_array($violations), + fn (ConstraintViolation $v) => preg_match(sprintf("/^(%s)$/", implode('|', $ignored_validation_constraint_messages)), (string) $v->getMessage()) !== 1 + ); + $validation_errors = array_map( + fn (ConstraintViolation $v) => sprintf("[%s] %s", $v->getPropertyPath(), (string) $v->getMessage()), + $filtered_violations + ); + $errors = array_merge($errors, $validation_errors); if (empty($errors)) { return TRUE; } diff --git a/core/modules/block/config/schema/block.schema.yml b/core/modules/block/config/schema/block.schema.yml index 64dda31dbbb7..8989c1d79efe 100644 --- a/core/modules/block/config/schema/block.schema.yml +++ b/core/modules/block/config/schema/block.schema.yml @@ -11,7 +11,9 @@ block.block.*: # @see https://www.drupal.org/project/drupal/issues/2685917 # @see https://www.drupal.org/project/drupal/issues/2043527 constraints: - Regex: '/^[a-z0-9_.]+$/' + Regex: + pattern: '/^[a-z0-9_.]+$/' + message: "The %value machine name is not valid." theme: type: string label: 'Theme' diff --git a/core/modules/block/tests/src/Functional/BlockTest.php b/core/modules/block/tests/src/Functional/BlockTest.php index bc6fcded6fc9..1b31a09e3863 100644 --- a/core/modules/block/tests/src/Functional/BlockTest.php +++ b/core/modules/block/tests/src/Functional/BlockTest.php @@ -580,6 +580,7 @@ class BlockTest extends BlockTestBase { $block = Block::create([ 'id' => $this->randomMachineName(), 'plugin' => 'system_powered_by_block', + 'theme' => 'stark', ]); $block->setVisibilityConfig('user_role', [ diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php index fe416950448b..1a18d7d59274 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php @@ -69,7 +69,7 @@ class BlockContentDeletionTest extends KernelTestBase { $block_content->save(); $plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid(); - $block = $this->placeBlock($plugin_id, ['region' => 'content']); + $block = $this->placeBlock($plugin_id, ['region' => 'content', 'theme' => 'stark']); // Delete it via storage. $storage = $this->container->get('entity_type.manager')->getStorage('block_content'); diff --git a/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php b/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php index 66668eed18eb..03905f04f495 100644 --- a/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php +++ b/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php @@ -12,7 +12,6 @@ use Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Config\Schema\SchemaCheckTrait; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; @@ -50,8 +49,6 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; */ class CKEditor5 extends EditorBase implements ContainerFactoryPluginInterface { - use SchemaCheckTrait; - /** * The CKEditor plugin manager. * @@ -729,8 +726,6 @@ class CKEditor5 extends EditorBase implements ContainerFactoryPluginInterface { // ::getGeneratedAllowedHtmlValue(), to update filter_html's // "allowed_html". $form_state->set('ckeditor5_validated_pair', $eventual_editor_and_format); - - assert(TRUE === $this->checkConfigSchema(\Drupal::getContainer()->get('config.typed'), 'editor.editor.id_does_not_matter', $submitted_editor->toArray()), 'Schema errors: ' . print_r($this->checkConfigSchema(\Drupal::getContainer()->get('config.typed'), 'editor.editor.id_does_not_matter', $submitted_editor->toArray()), TRUE)); } /** diff --git a/core/modules/ckeditor5/tests/src/Functional/ImageUploadTest.php b/core/modules/ckeditor5/tests/src/Functional/ImageUploadTest.php index fdd7235e44af..63834751f3b1 100644 --- a/core/modules/ckeditor5/tests/src/Functional/ImageUploadTest.php +++ b/core/modules/ckeditor5/tests/src/Functional/ImageUploadTest.php @@ -224,7 +224,11 @@ class ImageUploadTest extends BrowserTestBase { 'drupalInsertImage', ], ], - 'plugins' => [], + 'plugins' => [ + 'ckeditor5_imageResize' => [ + 'allow_resize' => FALSE, + ], + ], ], 'image_upload' => $upload_config, ]); diff --git a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php index 47aa70dcd9e1..0dbd9faa9d85 100644 --- a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php +++ b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php @@ -82,6 +82,16 @@ class CKEditor5PluginManagerTest extends KernelTestBase { $this->typedConfig = $this->container->get('config.typed'); } + /** + * {@inheritdoc} + */ + protected function enableModules(array $modules) { + parent::enableModules($modules); + // Ensure the CKEditor 5 plugin manager instance on the test reflects the + // status after the module is installed. + $this->manager = $this->container->get('plugin.manager.ckeditor5.plugin'); + } + /** * Mocks a module providing a CKEditor 5 plugin in VFS. * diff --git a/core/modules/ckeditor5/tests/src/Kernel/SmartDefaultSettingsTest.php b/core/modules/ckeditor5/tests/src/Kernel/SmartDefaultSettingsTest.php index 6d2cb07bc5e9..04581130800c 100644 --- a/core/modules/ckeditor5/tests/src/Kernel/SmartDefaultSettingsTest.php +++ b/core/modules/ckeditor5/tests/src/Kernel/SmartDefaultSettingsTest.php @@ -541,6 +541,13 @@ class SmartDefaultSettingsTest extends KernelTestBase { // Ensure that the result of ::computeSmartDefaultSettings() always complies // with the config schema. + // TRICKY: because we're validating using `editor.editor.*` as the config + // name, TextEditorObjectDependentValidatorTrait will load the stored filter + // format. That has not yet been updated at this point, so in order for + // validation to pass, it must first be saved. + // @see \Drupal\ckeditor5\Plugin\Validation\Constraint\TextEditorObjectDependentValidatorTrait::createTextEditorObjectFromContext() + // @todo Remove this work-around in https://www.drupal.org/project/drupal/issues/3231354 + $updated_text_editor->getFilterFormat()->save(); $this->assertConfigSchema( $this->typedConfig, $updated_text_editor->getConfigDependencyName(), diff --git a/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php b/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php index 16004e8a2b0f..454826a67f2b 100644 --- a/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php +++ b/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php @@ -595,20 +595,15 @@ class ValidatorsTest extends KernelTestBase { ->set('name', $this->randomString()) ->save(); - $this->assertConfigSchema( - $this->typedConfig, - $text_editor->getConfigDependencyName(), - $text_editor->toArray() - ); - // TRICKY: only validate the Editor entity in isolation if we expect NO - // violations: when violations are expected, this would just find the very - // violations that the next assertion is checking. - // @todo Remove in https://www.drupal.org/project/drupal/issues/3361534, which moves this into ::assertConfigSchema() + // TRICKY: only assert config schema (and validation constraints) if we + // expect NO violations: when violations are expected, this would just find + // the very violations that the next assertion is checking. if (empty($expected_violations)) { - $this->assertSame([], array_map( - fn ($v) => sprintf("[%s] %s", $v->getPropertyPath(), (string) $v->getMessage()), - iterator_to_array($this->typedConfig->createFromNameAndData($text_editor->getConfigDependencyName(), $text_editor->toArray())->validate()) - )); + $this->assertConfigSchema( + $this->typedConfig, + $text_editor->getConfigDependencyName(), + $text_editor->toArray() + ); } $this->assertSame($expected_violations, $this->validatePairToViolationsArray($text_editor, $text_format, TRUE)); diff --git a/core/modules/config/tests/config_test/config/install/config_test.validation.yml b/core/modules/config/tests/config_test/config/install/config_test.validation.yml index fa84ff392bd7..f86f480297e3 100644 --- a/core/modules/config/tests/config_test/config/install/config_test.validation.yml +++ b/core/modules/config/tests/config_test/config/install/config_test.validation.yml @@ -6,3 +6,4 @@ giraffe: hum1: hum1 hum2: hum2 uuid: '7C30C50E-641A-4E34-A7F1-46BCFB9BE5A3' +langcode: en diff --git a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php index c04c0eb9acac..10e6d4710140 100644 --- a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php +++ b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php @@ -11,6 +11,9 @@ use Drupal\Tests\system\Functional\Module\ModuleTestBase; /** * Tests the largest configuration import possible with all available modules. * + * Note that the use of SchemaCheckTestTrait means that the schema conformance + * of all default configuration is also tested. + * * @group config */ class ConfigImportAllTest extends ModuleTestBase { diff --git a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php index 05781754b121..7ee4a19a4517 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php @@ -204,7 +204,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase { // Remove the field default from articles. $default_image_settings = $field->getSetting('default_image'); - $default_image_settings['uuid'] = 0; + $default_image_settings['uuid'] = \Drupal::service('uuid')->generate(); $field->setSetting('default_image', $default_image_settings); $field->save(); diff --git a/core/modules/language/config/schema/language.schema.yml b/core/modules/language/config/schema/language.schema.yml index 4516a6fe057c..cef2ad02a008 100644 --- a/core/modules/language/config/schema/language.schema.yml +++ b/core/modules/language/config/schema/language.schema.yml @@ -120,6 +120,10 @@ language.content_settings.*.*: default_langcode: type: langcode label: 'Default language' + constraints: + Choice: + # The "default language" has a different list of allowed values. + callback: '\Drupal\language\Entity\ContentLanguageSettings::getAllValidDefaultLangcodes' language_alterable: type: boolean label: 'Allow to alter the language' diff --git a/core/modules/language/src/Entity/ContentLanguageSettings.php b/core/modules/language/src/Entity/ContentLanguageSettings.php index d7c549924ed0..00e8d1fa69cb 100644 --- a/core/modules/language/src/Entity/ContentLanguageSettings.php +++ b/core/modules/language/src/Entity/ContentLanguageSettings.php @@ -214,4 +214,25 @@ class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguag return $this; } + /** + * Returns all valid values for the `default_langcode` property. + * + * @return string[] + * All possible valid default langcodes. This includes all langcodes in the + * standard list of human languages, along with special langcodes like + * `site_default`, `current_interface` and `authors_default`. + * + * @see \Drupal\language\Element\LanguageConfiguration::getDefaultOptions() + * @see \Drupal\Core\TypedData\Plugin\DataType\LanguageReference::getAllValidLangcodes() + */ + public static function getAllValidDefaultLangcodes(): array { + $language_manager = \Drupal::service('language_manager'); + return array_unique([ + ...array_keys($language_manager->getLanguages(LanguageInterface::STATE_ALL)), + LanguageInterface::LANGCODE_SITE_DEFAULT, + 'current_interface', + 'authors_default', + ]); + } + } diff --git a/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php b/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php index 900b0c9dc455..b21453d13bfb 100644 --- a/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php @@ -42,6 +42,15 @@ use Drupal\block\Entity\Block; */ class LanguageUILanguageNegotiationTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected static $configSchemaCheckerExclusions = [ + // Necessary to allow setting `selected_langcode` to NULL. + // @see testUILanguageNegotiation() + 'language.negotiation', + ]; + /** * The admin user for testing. * diff --git a/core/modules/media/config/install/media.settings.yml b/core/modules/media/config/install/media.settings.yml index 32c0935743e0..d0fb40c6e810 100644 --- a/core/modules/media/config/install/media.settings.yml +++ b/core/modules/media/config/install/media.settings.yml @@ -1,4 +1,4 @@ icon_base_uri: 'public://media-icons/generic' -iframe_domain: '' +iframe_domain: ~ oembed_providers_url: 'https://oembed.com/providers.json' standalone_url: false diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml index 29c56d37502e..f9a5e089751d 100644 --- a/core/modules/media/config/schema/media.schema.yml +++ b/core/modules/media/config/schema/media.schema.yml @@ -7,6 +7,7 @@ media.settings: label: 'Full URI to a folder where the media icons will be installed' iframe_domain: type: uri + nullable: true label: 'Domain from which to serve oEmbed content in an iframe' oembed_providers_url: type: uri diff --git a/core/modules/media/media.post_update.php b/core/modules/media/media.post_update.php index 12b5709b8765..8490dc60eaa1 100644 --- a/core/modules/media/media.post_update.php +++ b/core/modules/media/media.post_update.php @@ -33,3 +33,15 @@ function media_post_update_oembed_loading_attribute(array &$sandbox = NULL): voi return $media_config_updater->processOembedEagerLoadField($view_display); }); } + +/** + * Updates media.settings:iframe_domain config if it's still at the default. + */ +function media_post_update_set_blank_iframe_domain_to_null() { + $media_settings = \Drupal::configFactory()->getEditable('media.settings'); + if ($media_settings->get('iframe_domain') === '') { + $media_settings + ->set('iframe_domain', NULL) + ->save(TRUE); + } +} diff --git a/core/modules/media/src/Form/MediaSettingsForm.php b/core/modules/media/src/Form/MediaSettingsForm.php index 6d90e36ee42a..b70aa19d24f6 100644 --- a/core/modules/media/src/Form/MediaSettingsForm.php +++ b/core/modules/media/src/Form/MediaSettingsForm.php @@ -117,8 +117,13 @@ class MediaSettingsForm extends ConfigFormBase { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { + $iframe_domain = $form_state->getValue('iframe_domain'); + // The empty string is not a valid URI, but NULL is allowed. + if ($iframe_domain === '') { + $iframe_domain = NULL; + } $this->config('media.settings') - ->set('iframe_domain', $form_state->getValue('iframe_domain')) + ->set('iframe_domain', $iframe_domain) ->set('standalone_url', $form_state->getValue('standalone_url')) ->save(); diff --git a/core/modules/media/tests/fixtures/update/media.php b/core/modules/media/tests/fixtures/update/media.php index df61cc34e583..895e1a3a0699 100644 --- a/core/modules/media/tests/fixtures/update/media.php +++ b/core/modules/media/tests/fixtures/update/media.php @@ -50,3 +50,21 @@ $connection->update('key_value') ->condition('name', 'existing_updates') ->execute(); +// Create media.settings. +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'media.settings', + 'data' => serialize([ + 'icon_base_uri' => 'public://media-icons/generic', + 'iframe_domain' => '', + 'oembed_providers_url' => 'https://oembed.com/providers.json', + 'standalone_url' => FALSE, + ]), + ]) + ->execute(); diff --git a/core/modules/media/tests/src/Functional/MediaSettingsTest.php b/core/modules/media/tests/src/Functional/MediaSettingsTest.php index 64b85db2508b..06df4a9f9ab7 100644 --- a/core/modules/media/tests/src/Functional/MediaSettingsTest.php +++ b/core/modules/media/tests/src/Functional/MediaSettingsTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\media\Functional; +use Drupal\Core\Url; + /** * Testing the media settings. * @@ -19,7 +21,10 @@ class MediaSettingsTest extends MediaFunctionalTestBase { */ protected function setUp(): void { parent::setUp(); - $this->drupalLogin($this->createUser(['administer site configuration'])); + $this->drupalLogin($this->createUser([ + 'administer site configuration', + 'administer media', + ])); } /** @@ -37,4 +42,25 @@ class MediaSettingsTest extends MediaFunctionalTestBase { $assert_session->pageTextContains('It is potentially insecure to display oEmbed content in a frame'); } + /** + * Tests that the media settings form stores a `null` iFrame domain. + */ + public function testSettingsForm(): void { + $assert_session = $this->assertSession(); + + $this->assertNull($this->config('media.settings')->get('iframe_domain')); + $this->drupalGet(Url::fromRoute('media.settings')); + $assert_session->fieldExists('iframe_domain'); + + // Explicitly submitting an empty string does not result in the + // `iframe_domain` property getting set to the empty string: it is converted + // to `null` to comply with the config schema. + // @see \Drupal\media\Form\MediaSettingsForm::submitForm() + $this->submitForm([ + 'iframe_domain' => '', + ], 'Save configuration'); + $assert_session->statusMessageContains('The configuration options have been saved.', 'status'); + $this->assertNull($this->config('media.settings')->get('iframe_domain')); + } + } diff --git a/core/modules/media/tests/src/Functional/Update/MediaSettingsDefaultIframeDomainUpdateTest.php b/core/modules/media/tests/src/Functional/Update/MediaSettingsDefaultIframeDomainUpdateTest.php new file mode 100644 index 000000000000..d3367016a5d8 --- /dev/null +++ b/core/modules/media/tests/src/Functional/Update/MediaSettingsDefaultIframeDomainUpdateTest.php @@ -0,0 +1,60 @@ +databaseDumpFiles = [ + DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz', + __DIR__ . '/../../../fixtures/update/media.php', + ]; + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + // Because the test manually installs media module, the entity type config + // must be manually installed similar to kernel tests. + $entity_type_manager = \Drupal::entityTypeManager(); + $media = $entity_type_manager->getDefinition('media'); + \Drupal::service('entity_type.listener')->onEntityTypeCreate($media); + $media_type = $entity_type_manager->getDefinition('media_type'); + \Drupal::service('entity_type.listener')->onEntityTypeCreate($media_type); + } + + /** + * Tests update of media.settings:iframe_domain. + */ + public function testUpdate() { + $iframe_domain_before = $this->config('media.settings')->get('iframe_domain'); + $this->assertSame('', $iframe_domain_before); + + $this->runUpdates(); + + $iframe_domain_after = $this->config('media.settings')->get('iframe_domain'); + $this->assertNull($iframe_domain_after); + } + +} diff --git a/core/modules/shortcut/config/schema/shortcut.schema.yml b/core/modules/shortcut/config/schema/shortcut.schema.yml index 6fceb5741e27..8f32203c652d 100644 --- a/core/modules/shortcut/config/schema/shortcut.schema.yml +++ b/core/modules/shortcut/config/schema/shortcut.schema.yml @@ -11,7 +11,9 @@ shortcut.set.*: # dashes but not underscores. # @see \Drupal\shortcut\ShortcutSetForm::form() constraints: - Regex: '/^[a-z0-9-]+$/' + Regex: + pattern: '/^[a-z0-9-]+$/' + message: "The %value machine name is not valid." Length: max: 23 label: diff --git a/core/modules/system/config/install/system.theme.global.yml b/core/modules/system/config/install/system.theme.global.yml index b8da4238c22d..598d6bd0cd8c 100644 --- a/core/modules/system/config/install/system.theme.global.yml +++ b/core/modules/system/config/install/system.theme.global.yml @@ -10,5 +10,5 @@ features: node_user_picture: true logo: path: '' - url: '' + url: ~ use_default: true diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 2c01a98156c7..e6a5260d2b6e 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -215,7 +215,9 @@ system.menu.*: # underscores. # @see \Drupal\menu_ui\MenuForm::form() constraints: - Regex: '/^[a-z0-9-]+$/' + Regex: + pattern: '/^[a-z0-9-]+$/' + message: "The %value machine name is not valid." Length: max: 32 label: diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 850def033ed0..14fd7bfc01ba 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -146,3 +146,15 @@ function system_post_update_add_description_to_entity_form_mode(array &$sandbox $config_entity_updater->update($sandbox, 'entity_form_mode', $callback); } + +/** + * Updates system.theme.global:logo.url config if it's still at the default. + */ +function system_post_update_set_blank_log_url_to_null() { + $global_theme_settings = \Drupal::configFactory()->getEditable('system.theme.global'); + if ($global_theme_settings->get('logo.url') === '') { + $global_theme_settings + ->set('logo.url', NULL) + ->save(TRUE); + } +} diff --git a/core/modules/system/tests/src/Functional/Update/GlobalThemeSettingsDefaultLogoUrlUpdateTest.php b/core/modules/system/tests/src/Functional/Update/GlobalThemeSettingsDefaultLogoUrlUpdateTest.php new file mode 100644 index 000000000000..d111156387ce --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/GlobalThemeSettingsDefaultLogoUrlUpdateTest.php @@ -0,0 +1,45 @@ +databaseDumpFiles = [ + DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz', + ]; + } + + /** + * Tests update of system.theme.global:logo.url. + */ + public function testUpdate() { + $logo_url_before = $this->config('system.theme.global')->get('logo.url'); + $this->assertSame('', $logo_url_before); + + $this->runUpdates(); + + $logo_url_after = $this->config('system.theme.global')->get('logo.url'); + $this->assertNull($logo_url_after); + } + +} diff --git a/core/modules/update/config/install/update.settings.yml b/core/modules/update/config/install/update.settings.yml index ded8c9d24c01..2e4f3fb314bb 100644 --- a/core/modules/update/config/install/update.settings.yml +++ b/core/modules/update/config/install/update.settings.yml @@ -2,7 +2,7 @@ check: disabled_extensions: false interval_days: 1 fetch: - url: '' + url: ~ max_attempts: 2 timeout: 30 notification: diff --git a/core/modules/update/config/schema/update.schema.yml b/core/modules/update/config/schema/update.schema.yml index e9da9268e742..06eeb49c3673 100644 --- a/core/modules/update/config/schema/update.schema.yml +++ b/core/modules/update/config/schema/update.schema.yml @@ -20,6 +20,7 @@ update.settings: mapping: url: type: uri + nullable: true label: 'URL for fetching available update data' max_attempts: type: integer diff --git a/core/modules/update/tests/src/Functional/Update/UpdateSettingsDefaultFetchUrlUpdateTest.php b/core/modules/update/tests/src/Functional/Update/UpdateSettingsDefaultFetchUrlUpdateTest.php new file mode 100644 index 000000000000..cc1880451c55 --- /dev/null +++ b/core/modules/update/tests/src/Functional/Update/UpdateSettingsDefaultFetchUrlUpdateTest.php @@ -0,0 +1,110 @@ +databaseDumpFiles = [ + DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz', + ]; + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $connection = Database::getConnection(); + + // Set the schema version. + $connection->merge('key_value') + ->fields([ + 'value' => 'i:8001;', + 'name' => 'update', + 'collection' => 'system.schema', + ]) + ->condition('collection', 'system.schema') + ->condition('name', 'update') + ->execute(); + + // Update core.extension. + $extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); + $extensions = unserialize($extensions); + $extensions['module']['update'] = 0; + $connection->update('config') + ->fields(['data' => serialize($extensions)]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + + // Create update.settings config. + $default_update_settings = [ + 'check' => [ + 'disabled_extensions' => FALSE, + 'interval_days' => 1, + ], + 'fetch' => [ + 'url' => '', + 'max_attempts' => 2, + 'timeout' => 30, + ], + 'notification' => [ + 'emails' => [], + 'threshold' => 'all', + ], + ]; + $connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'update.settings', + 'data' => serialize($default_update_settings), + ]) + ->execute(); + } + + /** + * Tests update of update.settings:fetch.url. + */ + public function testUpdate() { + $fetch_url_before = $this->config('update.settings')->get('fetch.url'); + $this->assertSame('', $fetch_url_before); + + $this->runUpdates(); + + $fetch_url_after = $this->config('update.settings')->get('fetch.url'); + $this->assertNull($fetch_url_after); + } + +} diff --git a/core/modules/update/update.post_update.php b/core/modules/update/update.post_update.php index 15fadacb1738..2af7876c0dee 100644 --- a/core/modules/update/update.post_update.php +++ b/core/modules/update/update.post_update.php @@ -13,3 +13,15 @@ function update_remove_post_updates() { 'update_post_update_add_view_update_notifications_permission' => '10.0.0', ]; } + +/** + * Updates update.settings:fetch.url config if it's still at the default. + */ +function update_post_update_set_blank_fetch_url_to_null() { + $update_settings = \Drupal::configFactory()->getEditable('update.settings'); + if ($update_settings->get('fetch.url') === '') { + $update_settings + ->set('fetch.url', NULL) + ->save(TRUE); + } +} diff --git a/core/modules/views/tests/src/Kernel/TestViewsTest.php b/core/modules/views/tests/src/Kernel/TestViewsTest.php index 7ca4d888b51c..481e71c375d4 100644 --- a/core/modules/views/tests/src/Kernel/TestViewsTest.php +++ b/core/modules/views/tests/src/Kernel/TestViewsTest.php @@ -37,6 +37,7 @@ class TestViewsTest extends KernelTestBase { \Drupal::service('module_handler'), \Drupal::service('class_resolver') ); + $typed_config->setValidationConstraintManager(\Drupal::service('validation.constraint')); // Create a configuration storage with access to default configuration in // every module, profile and theme. diff --git a/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php index c236fde718df..a948fbc7ba20 100644 --- a/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php +++ b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php @@ -26,7 +26,7 @@ class AreaEntityUITest extends UITestBase { public function testUI() { // Set up a block and an entity_test entity. - $block = Block::create(['id' => 'test_id', 'plugin' => 'system_main_block']); + $block = Block::create(['id' => 'test_id', 'plugin' => 'system_main_block', 'theme' => 'stark']); $block->save(); $entity_test = EntityTest::create(['bundle' => 'entity_test']); diff --git a/core/profiles/demo_umami/config/install/editor.editor.basic_html.yml b/core/profiles/demo_umami/config/install/editor.editor.basic_html.yml index bea27d77b9ca..2b1d2c54e051 100644 --- a/core/profiles/demo_umami/config/install/editor.editor.basic_html.yml +++ b/core/profiles/demo_umami/config/install/editor.editor.basic_html.yml @@ -33,6 +33,9 @@ settings: - heading4 - heading5 - heading6 + ckeditor5_list: + reversed: false + startIndex: false ckeditor5_sourceEditing: allowed_tags: - '' @@ -50,6 +53,8 @@ settings: - '
' - '' - '' + media_media: + allow_view_mode_override: false image_upload: status: false scheme: public diff --git a/core/profiles/demo_umami/config/install/editor.editor.full_html.yml b/core/profiles/demo_umami/config/install/editor.editor.full_html.yml index 7862acf65e9d..20161140e479 100644 --- a/core/profiles/demo_umami/config/install/editor.editor.full_html.yml +++ b/core/profiles/demo_umami/config/install/editor.editor.full_html.yml @@ -38,8 +38,13 @@ settings: - heading4 - heading5 - heading6 + ckeditor5_list: + reversed: false + startIndex: false ckeditor5_sourceEditing: allowed_tags: { } + media_media: + allow_view_mode_override: false image_upload: status: true scheme: public diff --git a/core/profiles/demo_umami/config/install/system.theme.global.yml b/core/profiles/demo_umami/config/install/system.theme.global.yml index 52e499a99315..70080454c85b 100644 --- a/core/profiles/demo_umami/config/install/system.theme.global.yml +++ b/core/profiles/demo_umami/config/install/system.theme.global.yml @@ -10,5 +10,5 @@ features: node_user_picture: true logo: path: '' - url: '' + url: ~ use_default: true diff --git a/core/profiles/minimal/config/install/system.theme.global.yml b/core/profiles/minimal/config/install/system.theme.global.yml index 0fdc4b977eed..243194347b17 100644 --- a/core/profiles/minimal/config/install/system.theme.global.yml +++ b/core/profiles/minimal/config/install/system.theme.global.yml @@ -10,5 +10,5 @@ features: node_user_picture: false logo: path: '' - url: '' + url: ~ use_default: true diff --git a/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php index cba4867947ec..a9be4d4ce30d 100644 --- a/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php +++ b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php @@ -25,6 +25,11 @@ class TypedConfigTest extends KernelTestBase { */ protected static $modules = ['config_test']; + /** + * {@inheritdoc} + */ + protected static $configSchemaCheckerExclusions = ['config_test.validation']; + /** * {@inheritdoc} */ @@ -90,7 +95,7 @@ class TypedConfigTest extends KernelTestBase { $typed_config_manager = \Drupal::service('config.typed'); $typed_config = $typed_config_manager->createFromNameAndData('config_test.validation', \Drupal::configFactory()->get('config_test.validation')->get()); $this->assertInstanceOf(TypedConfigInterface::class, $typed_config); - $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid'], array_keys($typed_config->getElements())); + $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid', 'langcode'], array_keys($typed_config->getElements())); $this->assertSame('config_test.validation', $typed_config->getName()); $this->assertSame('config_test.validation', $typed_config->getPropertyPath()); $this->assertSame('config_test.validation.llama', $typed_config->get('llama')->getPropertyPath()); diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php index b0eafccf14db..c0cf4928c68f 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php @@ -106,14 +106,18 @@ abstract class ConfigEntityValidationTestBase extends KernelTestBase { $constraints = $this->getMachineNameConstraints(); $this->assertNotEmpty($constraints['Regex']); - $this->assertIsString($constraints['Regex']); + $this->assertIsArray($constraints['Regex']); + $this->assertArrayHasKey('pattern', $constraints['Regex']); + $this->assertIsString($constraints['Regex']['pattern']); + $this->assertArrayHasKey('message', $constraints['Regex']); + $this->assertIsString($constraints['Regex']['message']); $id_key = $this->entity->getEntityType()->getKey('id'); if ($is_expected_to_be_valid) { $expected_errors = []; } else { - $expected_errors = [$id_key => 'This value is not valid.']; + $expected_errors = [$id_key => sprintf('The "%s" machine name is not valid.', $machine_name)]; } $this->entity->set( diff --git a/core/tests/Drupal/KernelTests/Core/Config/DefaultConfigTest.php b/core/tests/Drupal/KernelTests/Core/Config/DefaultConfigTest.php deleted file mode 100644 index 483d104c8ca9..000000000000 --- a/core/tests/Drupal/KernelTests/Core/Config/DefaultConfigTest.php +++ /dev/null @@ -1,76 +0,0 @@ -register('default_config_test.schema_storage') - ->setClass('\Drupal\config_test\TestInstallStorage') - ->addArgument(InstallStorage::CONFIG_SCHEMA_DIRECTORY); - - $definition = $container->getDefinition('config.typed'); - $definition->replaceArgument(1, new Reference('default_config_test.schema_storage')); - } - - /** - * Tests default configuration data type. - */ - public function testDefaultConfig() { - $typed_config = \Drupal::service('config.typed'); - // Create a configuration storage with access to default configuration in - // every module, profile and theme. - $default_config_storage = new TestInstallStorage(); - foreach ($default_config_storage->listAll() as $config_name) { - if (in_array($config_name, $this->toSkip)) { - continue; - } - - $data = $default_config_storage->read($config_name); - $this->assertConfigSchema($typed_config, $config_name, $data); - } - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/Config/SchemaCheckTraitTest.php b/core/tests/Drupal/KernelTests/Core/Config/SchemaCheckTraitTest.php index 97699eeb992e..bd89523a54df 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/SchemaCheckTraitTest.php +++ b/core/tests/Drupal/KernelTests/Core/Config/SchemaCheckTraitTest.php @@ -56,9 +56,14 @@ class SchemaCheckTraitTest extends KernelTestBase { $config_data['boolean'] = []; $ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data); $expected = [ + // Storage type check errors. + // @see \Drupal\Core\Config\Schema\SchemaCheckTrait::checkValue() 'config_test.types:new_key' => 'missing schema', 'config_test.types:new_array' => 'missing schema', 'config_test.types:boolean' => 'non-scalar value but not defined as an array (such as mapping or sequence)', + // Validation constraints violations. + // @see \Drupal\Core\TypedData\TypedDataInterface::validate() + '0' => '[boolean] This value should be of the correct primitive type.', ]; $this->assertEquals($expected, $ret); }