diff --git a/core/modules/book/config/optional/node.type.book.yml b/core/modules/book/config/optional/node.type.book.yml index 0c07a79e8f9d3682e8515135dd95c1bd190df5ff..9722037d8282c3c28949616a50dda34c794db145 100644 --- a/core/modules/book/config/optional/node.type.book.yml +++ b/core/modules/book/config/optional/node.type.book.yml @@ -7,7 +7,7 @@ dependencies: name: 'Book page' type: book description: '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/modules/file/tests/modules/file_test_views/config/optional/node.type.file_test.yml b/core/modules/file/tests/modules/file_test_views/config/optional/node.type.file_test.yml index d5efeed2110c640d54343a5dc387eeffa27db691..1e062ec83c907c102fed147bcd84c536f9b52cd8 100644 --- a/core/modules/file/tests/modules/file_test_views/config/optional/node.type.file_test.yml +++ b/core/modules/file/tests/modules/file_test_views/config/optional/node.type.file_test.yml @@ -1,7 +1,7 @@ type: file_test name: File test description: 'Bundle for testing file relationships.' -help: '' +help: null new_revision: true display_submitted: true preview_mode: 1 diff --git a/core/modules/forum/config/optional/node.type.forum.yml b/core/modules/forum/config/optional/node.type.forum.yml index 8ed965df3fb6e5f2878597671736c6e02f46c3d9..2bb8ed367c41f0294e2e122a6bc4db3ff5b42301 100644 --- a/core/modules/forum/config/optional/node.type.forum.yml +++ b/core/modules/forum/config/optional/node.type.forum.yml @@ -7,7 +7,7 @@ dependencies: name: 'Forum topic' type: forum description: 'A <em>forum topic</em> starts a new discussion thread within a forum.' -help: '' +help: null new_revision: false preview_mode: 1 display_submitted: true diff --git a/core/modules/language/tests/language_entity_field_access_test/config/install/node.type.page.yml b/core/modules/language/tests/language_entity_field_access_test/config/install/node.type.page.yml index 6ebd149e49becf3bda61b6b89c6a268818412b20..5b22929e2f4d86534f39a526ed4c0f67a080cb3a 100644 --- a/core/modules/language/tests/language_entity_field_access_test/config/install/node.type.page.yml +++ b/core/modules/language/tests/language_entity_field_access_test/config/install/node.type.page.yml @@ -3,8 +3,8 @@ status: true dependencies: { } name: page type: page -description: '' -help: '' +description: null +help: null new_revision: false preview_mode: 1 display_submitted: false diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.basic_page.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.basic_page.yml index 23abb54599c6dfe6517149dd96e1d0f8381eff8f..d89941abbd0a122b68d8f380c2662199538817c1 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.basic_page.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.basic_page.yml @@ -2,8 +2,8 @@ langcode: en status: true name: 'Basic Page' type: basic_page -description: '' -help: '' +description: null +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/modules/migrate/tests/modules/migrate_high_water_test/config/install/node.type.sql_import_node.yml b/core/modules/migrate/tests/modules/migrate_high_water_test/config/install/node.type.sql_import_node.yml index 55c0888e6cf46a4cb635a10891232d7a1f1c19f0..b024f89598a14dc1d3485cb0222f8da9c4e6b249 100644 --- a/core/modules/migrate/tests/modules/migrate_high_water_test/config/install/node.type.sql_import_node.yml +++ b/core/modules/migrate/tests/modules/migrate_high_water_test/config/install/node.type.sql_import_node.yml @@ -2,8 +2,8 @@ langcode: en status: true name: High Water import node type: high_water_import_node -description: '' -help: '' +description: null +help: null new_revision: false preview_mode: 1 display_submitted: true diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml index 7ce661e745aa56dea3c6655d2eaf8aa4c5aa3a7d..87621d6e252bf3114d86d9a647a54b1c0e77a9de 100644 --- a/core/modules/node/config/schema/node.schema.yml +++ b/core/modules/node/config/schema/node.schema.yml @@ -11,6 +11,8 @@ node.settings: node.type.*: type: config_entity label: 'Content type' + constraints: + FullyValidatable: ~ mapping: name: type: required_label @@ -26,15 +28,28 @@ node.type.*: description: type: text label: 'Description' + nullable: true + constraints: + NotBlank: + allowNull: true help: type: text label: 'Explanation or submission guidelines' + nullable: true + constraints: + NotBlank: + allowNull: true new_revision: type: boolean label: 'Whether a new revision should be created by default' preview_mode: type: integer label: 'Preview before submitting' + constraints: + # These are the values of the DRUPAL_DISABLED, DRUPAL_OPTIONAL, and + # DRUPAL_REQUIRED constants. + # @see \Drupal\node\NodeTypeForm::form() + Choice: [0, 1, 2] display_submitted: type: boolean label: 'Display setting for author and date Submitted by post information' diff --git a/core/modules/node/node.post_update.php b/core/modules/node/node.post_update.php index 0e76fd8ed51c95f79a3117adf8d078f51bc37dc5..c02da56a16a0a08fb56f5cf8c73011f234938450 100644 --- a/core/modules/node/node.post_update.php +++ b/core/modules/node/node.post_update.php @@ -5,6 +5,27 @@ * Post update functions for Node. */ +use Drupal\Core\Config\Entity\ConfigEntityUpdater; +use Drupal\node\NodeTypeInterface; + +/** + * Converts empty `description` and `help` in content types to NULL. + */ +function node_post_update_set_node_type_description_and_help_to_null(array &$sandbox): void { + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'node_type', function (NodeTypeInterface $node_type): bool { + // Content types' `help` and `description` fields must be stored as NULL + // at the config level if they are empty. + if (trim($node_type->getDescription()) === '') { + $node_type->set('description', NULL); + } + if (trim($node_type->getHelp()) === '') { + $node_type->set('help', NULL); + } + return TRUE; + }); +} + /** * Implements hook_removed_post_updates(). */ diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php index 8662408aa8f35e4a173a6505f2f280378f7bba53..ff2cb28e359551d4156d7d9388aa87c324fcccca 100644 --- a/core/modules/node/src/Entity/NodeType.php +++ b/core/modules/node/src/Entity/NodeType.php @@ -78,16 +78,16 @@ class NodeType extends ConfigEntityBundleBase implements NodeTypeInterface { /** * A brief description of this node type. * - * @var string + * @var string|null */ - protected $description; + protected $description = NULL; /** * Help information shown to the user when creating a Node of this type. * - * @var string + * @var string|null */ - protected $help; + protected $help = NULL; /** * Default value of the 'Create new revision' checkbox of this node type. @@ -164,14 +164,14 @@ public function setPreviewMode($preview_mode) { * {@inheritdoc} */ public function getHelp() { - return $this->help; + return $this->help ?? ''; } /** * {@inheritdoc} */ public function getDescription() { - return $this->description; + return $this->description ?? ''; } /** diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php index 00f829af35ba891c72d4d9b0b4582cb0d18346d9..445f567c9009366bb89d7dc50554e5d602616143 100644 --- a/core/modules/node/src/NodeTypeForm.php +++ b/core/modules/node/src/NodeTypeForm.php @@ -201,6 +201,23 @@ public function validateForm(array &$form, FormStateInterface $form_state) { } } + /** + * {@inheritdoc} + */ + public function buildEntity(array $form, FormStateInterface $form_state) { + /** @var \Drupal\node\NodeTypeInterface $entity */ + $entity = parent::buildEntity($form, $form_state); + + // The description and help text cannot be empty strings. + if (trim($form_state->getValue('description')) === '') { + $entity->set('description', NULL); + } + if (trim($form_state->getValue('help')) === '') { + $entity->set('help', NULL); + } + return $entity; + } + /** * {@inheritdoc} */ diff --git a/core/modules/node/src/Plugin/migrate/destination/EntityNodeType.php b/core/modules/node/src/Plugin/migrate/destination/EntityNodeType.php index 152b9e46fc7b46abff8484f1ca12ee0b7d50c584..cdee1f5fcfa8e56271e755e0a2b7bc84ec33781e 100644 --- a/core/modules/node/src/Plugin/migrate/destination/EntityNodeType.php +++ b/core/modules/node/src/Plugin/migrate/destination/EntityNodeType.php @@ -12,6 +12,23 @@ */ class EntityNodeType extends EntityConfigBase { + /** + * {@inheritdoc} + */ + public function getEntity(Row $row, array $old_destination_id_values) { + /** @var \Drupal\node\NodeTypeInterface $node_type */ + $node_type = parent::getEntity($row, $old_destination_id_values); + + // Config schema does not allow description or help text to be empty. + if ($node_type->getDescription() === '') { + $node_type->set('description', NULL); + } + if ($node_type->getHelp() === '') { + $node_type->set('help', NULL); + } + return $node_type; + } + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/fixtures/update/remove-description-from-article-content-type.php b/core/modules/node/tests/fixtures/update/remove-description-from-article-content-type.php new file mode 100644 index 0000000000000000000000000000000000000000..acbc1c95b21db7122223dba6a413fa7457625d82 --- /dev/null +++ b/core/modules/node/tests/fixtures/update/remove-description-from-article-content-type.php @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Empties the description of the `article` content type. + */ + +use Drupal\Core\Database\Database; + +$connection = Database::getConnection(); + +$data = $connection->select('config') + ->condition('name', 'node.type.article') + ->fields('config', ['data']) + ->execute() + ->fetchField(); +$data = unserialize($data); +$data['description'] = "\n"; +$connection->update('config') + ->condition('name', 'node.type.article') + ->fields([ + 'data' => serialize($data), + ]) + ->execute(); diff --git a/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml b/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml index cfb29c6d8cbfbe98a3ca29dead4ff12b4a87752a..c5c17fff30e042e0e4f5fac0304552578e438071 100644 --- a/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml +++ b/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml @@ -3,7 +3,7 @@ status: true name: Default type: default description: 'Default description.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/modules/node/tests/modules/node_test_config/sync/node.type.import.yml b/core/modules/node/tests/modules/node_test_config/sync/node.type.import.yml index f1bafe46d224d045d1bb153fb9c4241fa2ee248f..8b1b7cb5fc48e37aa3aa7a6182ffa1f95fa10954 100644 --- a/core/modules/node/tests/modules/node_test_config/sync/node.type.import.yml +++ b/core/modules/node/tests/modules/node_test_config/sync/node.type.import.yml @@ -1,7 +1,7 @@ type: import name: Import description: 'Import description.' -help: '' +help: null new_revision: false display_submitted: true preview_mode: 1 diff --git a/core/modules/node/tests/src/Functional/Update/NullHelpAndDescriptionTest.php b/core/modules/node/tests/src/Functional/Update/NullHelpAndDescriptionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..92811bc136cb5e9021e78524c69c4764f72724af --- /dev/null +++ b/core/modules/node/tests/src/Functional/Update/NullHelpAndDescriptionTest.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\Tests\node\Functional\Update; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\node\Entity\NodeType; + +/** + * Tests the upgrade path for making content types' help and description NULL. + * + * @group node + */ +class NullHelpAndDescriptionTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.filled.standard.php.gz', + __DIR__ . '/../../../fixtures/update/remove-description-from-article-content-type.php', + ]; + } + + /** + * Tests the upgrade path for updating empty help and description to NULL. + */ + public function testRunUpdates() { + $node_type = NodeType::load('article'); + $this->assertInstanceOf(NodeType::class, $node_type); + + $this->assertSame('', $node_type->get('help')); + $this->assertSame("\n", $node_type->get('description')); + $this->runUpdates(); + + $node_type = NodeType::load('article'); + $this->assertInstanceOf(NodeType::class, $node_type); + + $this->assertNull($node_type->get('help')); + $this->assertNull($node_type->get('description')); + $this->assertSame('', $node_type->getHelp()); + $this->assertSame('', $node_type->getDescription()); + } + +} diff --git a/core/modules/node/tests/src/Kernel/NodeTypeValidationTest.php b/core/modules/node/tests/src/Kernel/NodeTypeValidationTest.php index c5bcc47355e640dacae2516e98c20d8c582da1ff..e3c40bff49bef28e12e7c0f7555b673fbc37255f 100644 --- a/core/modules/node/tests/src/Kernel/NodeTypeValidationTest.php +++ b/core/modules/node/tests/src/Kernel/NodeTypeValidationTest.php @@ -19,6 +19,14 @@ class NodeTypeValidationTest extends ConfigEntityValidationTestBase { */ protected static $modules = ['field', 'node', 'text', 'user']; + /** + * {@inheritdoc} + */ + protected static array $propertiesWithOptionalValues = [ + 'description', + 'help', + ]; + /** * {@inheritdoc} */ @@ -28,4 +36,44 @@ protected function setUp(): void { $this->entity = $this->createContentType(); } + /** + * Tests that a node type's preview mode is constrained to certain values. + */ + public function testPreviewModeValidation(): void { + $this->entity->setPreviewMode(38); + $this->assertValidationErrors(['preview_mode' => 'The value you selected is not a valid choice.']); + + $this->entity->setPreviewMode(-1); + $this->assertValidationErrors(['preview_mode' => 'The value you selected is not a valid choice.']); + + $allowed_values = [ + DRUPAL_DISABLED, + DRUPAL_OPTIONAL, + DRUPAL_REQUIRED, + ]; + foreach ($allowed_values as $allowed_value) { + $this->entity->setPreviewMode($allowed_value); + $this->assertValidationErrors([]); + } + } + + /** + * Tests that description and help text can be NULL, but not empty strings. + */ + public function testDescriptionAndHelpCannotBeEmpty(): void { + $this->entity->set('description', NULL)->set('help', NULL); + // The entity's getters should cast NULL values to empty strings. + $this->assertSame('', $this->entity->getDescription()); + $this->assertSame('', $this->entity->getHelp()); + // But NULL values should be valid at the config level. + $this->assertValidationErrors([]); + + // But they cannot be empty strings, because that doesn't make sense. + $this->entity->set('description', '')->set('help', ''); + $this->assertValidationErrors([ + 'description' => 'This value should not be blank.', + 'help' => 'This value should not be blank.', + ]); + } + } diff --git a/core/modules/system/tests/modules/olivero_test/config/install/node.type.article.yml b/core/modules/system/tests/modules/olivero_test/config/install/node.type.article.yml index 1fd439ce71bf22657ff5b7e15805eb6f9b93ae05..ae8e9d12580b22cb2a38e40384818ac1bff2ce79 100644 --- a/core/modules/system/tests/modules/olivero_test/config/install/node.type.article.yml +++ b/core/modules/system/tests/modules/olivero_test/config/install/node.type.article.yml @@ -4,7 +4,7 @@ dependencies: { } name: Article type: article description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/profiles/demo_umami/config/install/node.type.article.yml b/core/profiles/demo_umami/config/install/node.type.article.yml index 1fd439ce71bf22657ff5b7e15805eb6f9b93ae05..ae8e9d12580b22cb2a38e40384818ac1bff2ce79 100644 --- a/core/profiles/demo_umami/config/install/node.type.article.yml +++ b/core/profiles/demo_umami/config/install/node.type.article.yml @@ -4,7 +4,7 @@ dependencies: { } name: Article type: article description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/profiles/demo_umami/config/install/node.type.page.yml b/core/profiles/demo_umami/config/install/node.type.page.yml index 57dcc0c9926f11a617ca22ba0e3073b426d20d66..176315b5824423a5445b4ea8ea9d8b0b8266138c 100644 --- a/core/profiles/demo_umami/config/install/node.type.page.yml +++ b/core/profiles/demo_umami/config/install/node.type.page.yml @@ -4,7 +4,7 @@ dependencies: { } name: 'Basic page' type: page description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false diff --git a/core/profiles/demo_umami/config/install/node.type.recipe.yml b/core/profiles/demo_umami/config/install/node.type.recipe.yml index 89ed3215c74ed18bd7057215681977097e360b1b..1810e77bcf292e37a3941adc5839f7ff5066f1bc 100644 --- a/core/profiles/demo_umami/config/install/node.type.recipe.yml +++ b/core/profiles/demo_umami/config/install/node.type.recipe.yml @@ -10,7 +10,7 @@ third_party_settings: name: Recipe type: recipe description: 'Add a new recipe to the site.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false diff --git a/core/profiles/nightwatch_a11y_testing/config/install/node.type.article.yml b/core/profiles/nightwatch_a11y_testing/config/install/node.type.article.yml index 1fd439ce71bf22657ff5b7e15805eb6f9b93ae05..ae8e9d12580b22cb2a38e40384818ac1bff2ce79 100644 --- a/core/profiles/nightwatch_a11y_testing/config/install/node.type.article.yml +++ b/core/profiles/nightwatch_a11y_testing/config/install/node.type.article.yml @@ -4,7 +4,7 @@ dependencies: { } name: Article type: article description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/profiles/nightwatch_a11y_testing/config/install/node.type.page.yml b/core/profiles/nightwatch_a11y_testing/config/install/node.type.page.yml index 57dcc0c9926f11a617ca22ba0e3073b426d20d66..176315b5824423a5445b4ea8ea9d8b0b8266138c 100644 --- a/core/profiles/nightwatch_a11y_testing/config/install/node.type.page.yml +++ b/core/profiles/nightwatch_a11y_testing/config/install/node.type.page.yml @@ -4,7 +4,7 @@ dependencies: { } name: 'Basic page' type: page description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false diff --git a/core/profiles/standard/config/install/node.type.article.yml b/core/profiles/standard/config/install/node.type.article.yml index 1fd439ce71bf22657ff5b7e15805eb6f9b93ae05..ae8e9d12580b22cb2a38e40384818ac1bff2ce79 100644 --- a/core/profiles/standard/config/install/node.type.article.yml +++ b/core/profiles/standard/config/install/node.type.article.yml @@ -4,7 +4,7 @@ dependencies: { } name: Article type: article description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/core/profiles/standard/config/install/node.type.page.yml b/core/profiles/standard/config/install/node.type.page.yml index 57dcc0c9926f11a617ca22ba0e3073b426d20d66..176315b5824423a5445b4ea8ea9d8b0b8266138c 100644 --- a/core/profiles/standard/config/install/node.type.page.yml +++ b/core/profiles/standard/config/install/node.type.page.yml @@ -4,7 +4,7 @@ dependencies: { } name: 'Basic page' type: page description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false