diff --git a/core/modules/block_content/block_content.module b/core/modules/block_content/block_content.module index 69717d6de6..72a51f30d8 100644 --- a/core/modules/block_content/block_content.module +++ b/core/modules/block_content/block_content.module @@ -94,7 +94,10 @@ function block_content_add_body_field($block_type_id, $label = 'Body') { 'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'), 'bundle' => $block_type_id, 'label' => $label, - 'settings' => ['display_summary' => FALSE], + 'settings' => [ + 'display_summary' => FALSE, + 'allowed_formats' => [], + ], ]); $field->save(); diff --git a/core/modules/block_content/migrations/block_content_body_field.yml b/core/modules/block_content/migrations/block_content_body_field.yml index 9ed884f99e..bc30e0ff98 100644 --- a/core/modules/block_content/migrations/block_content_body_field.yml +++ b/core/modules/block_content/migrations/block_content_body_field.yml @@ -13,6 +13,7 @@ source: field_name: body label: Body display_summary: false + allowed_formats: { } ids: entity_type: type: string diff --git a/core/modules/book/config/optional/field.field.node.book.body.yml b/core/modules/book/config/optional/field.field.node.book.body.yml index 7cf998dcf6..e45dcc27db 100644 --- a/core/modules/book/config/optional/field.field.node.book.body.yml +++ b/core/modules/book/config/optional/field.field.node.book.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php index 0cc527ebac..03204f08ff 100644 --- a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php +++ b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -81,7 +81,10 @@ protected function setUp(): void { 'field_storage' => $field_storage, 'bundle' => 'page', 'label' => 'Body', - 'settings' => ['display_summary' => TRUE], + 'settings' => [ + 'display_summary' => TRUE, + 'allowed_formats' => [], + ], 'required' => TRUE, ])->save(); diff --git a/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php b/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php index b5373b8fae..85a4aeb920 100644 --- a/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php +++ b/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php @@ -62,7 +62,10 @@ protected function setUp(): void { 'field_storage' => $field_storage, 'bundle' => 'page', 'label' => 'Body', - 'settings' => ['display_summary' => TRUE], + 'settings' => [ + 'display_summary' => TRUE, + 'allowed_formats' => [], + ], 'required' => TRUE, ])->save(); diff --git a/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php b/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php index 0cf5548b9c..9591438206 100644 --- a/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php +++ b/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php @@ -414,7 +414,10 @@ public function doFieldListTest() { 'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'), 'bundle' => $block_content_type->id(), 'label' => 'Body', - 'settings' => ['display_summary' => FALSE], + 'settings' => [ + 'display_summary' => FALSE, + 'allowed_formats' => [], + ], ]); $field->save(); diff --git a/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml b/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml index 4e0131622e..326f8d3ad2 100644 --- a/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml +++ b/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml @@ -15,3 +15,7 @@ editor.settings.trex: stumpy_arms: type: boolean label: 'Stumpy arms' + +field.field_settings.editor_test_text_long: + label: 'Filter test text (formatted, long) settings' + type: field.field_settings.text diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml index 3e9c0a4c52..6de8ad8290 100644 --- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml +++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml @@ -11,5 +11,6 @@ description: '' required: false default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml index faf99c3a89..a721251113 100644 --- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml +++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml @@ -11,5 +11,6 @@ description: '' required: false default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml index 95a19af3c7..cfa151f499 100644 --- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml +++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml @@ -11,5 +11,6 @@ description: '' required: false default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml index 678c9fa19a..b8af4db0e3 100644 --- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml +++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml @@ -9,7 +9,8 @@ description: '' required: '0' default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text dependencies: config: diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml index e483418f93..1f8f54fcba 100644 --- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml +++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml @@ -9,7 +9,8 @@ description: '' required: '0' default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text dependencies: config: diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml index 3afd2e255c..bc68be6378 100644 --- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml +++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml @@ -9,7 +9,8 @@ description: '' required: '0' default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text dependencies: config: diff --git a/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php b/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php index 025b820df1..f13eb9865d 100644 --- a/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php +++ b/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php @@ -82,7 +82,9 @@ protected function getExpectedNormalizedEntity() { 'label' => 'field_llama', 'langcode' => 'en', 'required' => FALSE, - 'settings' => [], + 'settings' => [ + 'allowed_formats' => [], + ], 'status' => TRUE, 'translatable' => TRUE, 'uuid' => $this->entity->uuid(), diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php index 47edfd6d64..489ef4af9c 100644 --- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php +++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php @@ -33,7 +33,7 @@ public function testFieldInstanceMigration() { $field = FieldConfig::load('node.story.field_test'); $this->assertSame('Text Field', $field->label()); // field_test is a text_long field, which have no settings. - $this->assertSame([], $field->getSettings()); + $this->assertSame(['allowed_formats' => []], $field->getSettings()); $this->assertSame('text for default value', $entity->field_test->value); // Test a number field. diff --git a/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml b/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml index 215199cc22..8c6fa4c571 100644 --- a/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml +++ b/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml @@ -16,5 +16,6 @@ required: true translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/modules/forum/config/optional/field.field.node.forum.body.yml b/core/modules/forum/config/optional/field.field.node.forum.body.yml index 4289cdb2b2..ac1fa82a5d 100644 --- a/core/modules/forum/config/optional/field.field.node.forum.body.yml +++ b/core/modules/forum/config/optional/field.field.node.forum.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php index eb81f50912..d5ed11bede 100644 --- a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php @@ -119,7 +119,7 @@ protected function getExpectedDocument() { 'label' => 'field_llama', 'langcode' => 'en', 'required' => FALSE, - 'settings' => [], + 'settings' => ['allowed_formats' => []], 'status' => TRUE, 'translatable' => TRUE, 'drupal_internal__id' => 'node.camelids.field_llama', diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 71cf4edad7..9661f9a44e 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -266,7 +266,10 @@ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') { 'field_storage' => $field_storage, 'bundle' => $type->id(), 'label' => $label, - 'settings' => ['display_summary' => TRUE], + 'settings' => [ + 'display_summary' => TRUE, + 'allowed_formats' => [], + ], ]); $field->save(); diff --git a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml index 8e21dfb0c0..70a991652c 100644 --- a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml +++ b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml @@ -17,4 +17,5 @@ default_value: { } default_value_callback: '' settings: display_summary: true + allowed_formats: { } field_type: text_with_summary diff --git a/core/modules/text/config/schema/text.schema.yml b/core/modules/text/config/schema/text.schema.yml index 08bc9aad87..e0fa5d3d83 100644 --- a/core/modules/text/config/schema/text.schema.yml +++ b/core/modules/text/config/schema/text.schema.yml @@ -19,6 +19,12 @@ field.storage_settings.text: field.field_settings.text: type: mapping label: 'Text (formatted) settings' + mapping: + allowed_formats: + type: sequence + label: 'Allowed text formats' + sequence: + type: string field.value.text: type: mapping @@ -33,11 +39,17 @@ field.value.text: field.storage_settings.text_long: label: 'Text (formatted, long) settings' - type: mapping + type: field.field_settings.text field.field_settings.text_long: label: 'Text (formatted, long) settings' type: mapping + mapping: + allowed_formats: + type: sequence + label: 'Allowed text formats' + sequence: + type: string field.value.text_long: type: mapping @@ -64,6 +76,11 @@ field.field_settings.text_with_summary: required_summary: type: boolean label: 'Require summary' + allowed_formats: + type: sequence + label: 'Allowed text formats' + sequence: + type: string field.value.text_with_summary: type: mapping diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php b/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php new file mode 100644 index 0000000000..80a2533c6d --- /dev/null +++ b/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php @@ -0,0 +1,35 @@ +getSetting('allowed_formats') && !empty($allowed_formats)) { + $field_name = $this->definition->getName(); + $submitted_values = $form_state->getValue([ + 'default_value_input', + $field_name, + ]); + foreach ($submitted_values as $delta => $value) { + if (!in_array($value['format'], $allowed_formats, TRUE)) { + $form_state->setErrorByName( + "default_value_input][{$field_name}][{$delta}][format", + $this->t("The selected text format is not allowed.") + ); + } + } + } + parent::defaultValuesFormValidate($element, $form, $form_state); + } + +} diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php index 47a23dcf77..8926a4acb6 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php @@ -14,7 +14,8 @@ * description = @Translation("This field stores a text with a text format."), * category = @Translation("Text"), * default_widget = "text_textfield", - * default_formatter = "text_default" + * default_formatter = "text_default", + * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList" * ) */ class TextItem extends TextItemBase { diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php index 3fdd385d1e..e40849cbcd 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php @@ -6,6 +6,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\TypedData\DataDefinition; /** @@ -13,6 +14,62 @@ */ abstract class TextItemBase extends FieldItemBase { + /** + * {@inheritdoc} + */ + public static function defaultFieldSettings() { + return ['allowed_formats' => []] + parent::defaultFieldSettings(); + } + + /** + * {@inheritdoc} + */ + public function fieldSettingsForm(array $form, FormStateInterface $form_state) { + $element = parent::fieldSettingsForm($form, $form_state); + $settings = $this->getSettings(); + + $element['allowed_formats'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Allowed text formats'), + '#options' => $this->get('format')->getPossibleOptions(), + '#default_value' => !empty($settings['allowed_formats']) ? $settings['allowed_formats'] : [], + '#description' => $this->t('Select the allowed text formats. If no formats are selected, all available text formats will be displayed to the user.'), + '#element_validate' => [[static::class, 'validateAllowedFormats']], + ]; + + return $element; + } + + /** + * Render API callback: Processes the allowed formats value. + * + * Ensure the element's value is an indexed array of selected format IDs. + * This function is assigned as an #element_validate callback. + * + * @see static::fieldSettingsForm() + */ + public static function validateAllowedFormats(array &$element, FormStateInterface $form_state) { + $value = array_values(array_filter($form_state->getValue($element['#parents']))); + $form_state->setValueForElement($element, $value); + } + + /** + * {@inheritdoc} + */ + public static function calculateDependencies(FieldDefinitionInterface $field_definition) { + // Add explicitly allowed formats as config dependencies. + $format_dependencies = []; + $dependencies = parent::calculateDependencies($field_definition); + if (!is_null($field_definition->getSetting('allowed_formats'))) { + $format_dependencies = array_map(function (string $format_id) { + return 'filter.format.' . $format_id; + }, $field_definition->getSetting('allowed_formats')); + } + $config = $dependencies['config'] ?? []; + $dependencies['config'] = array_merge($config, $format_dependencies); + return $dependencies; + } + /** * {@inheritdoc} */ @@ -22,7 +79,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel ->setRequired(TRUE); $properties['format'] = DataDefinition::create('filter_format') - ->setLabel(t('Text format')); + ->setLabel(t('Text format')) + ->setSetting('allowed_formats', $field_definition->getSetting('allowed_formats')); $properties['processed'] = DataDefinition::create('string') ->setLabel(t('Processed text')) diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php index 15e4eb6a84..ce35873326 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php @@ -13,7 +13,8 @@ * description = @Translation("This field stores a long text with a text format."), * category = @Translation("Text"), * default_widget = "text_textarea", - * default_formatter = "text_default" + * default_formatter = "text_default", + * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList" * ) */ class TextLongItem extends TextItemBase { diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php index 9437c381fc..088986d576 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php @@ -16,7 +16,8 @@ * description = @Translation("This field stores long text with a format and an optional summary."), * category = @Translation("Text"), * default_widget = "text_textarea_with_summary", - * default_formatter = "text_default" + * default_formatter = "text_default", + * list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList" * ) */ class TextWithSummaryItem extends TextItemBase { @@ -87,7 +88,7 @@ public function isEmpty() { * {@inheritdoc} */ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { - $element = []; + $element = parent::fieldSettingsForm($form, $form_state); $settings = $this->getSettings(); $element['display_summary'] = [ diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig new file mode 100644 index 0000000000..9437c381fc --- /dev/null +++ b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig @@ -0,0 +1,128 @@ + 0, + 'required_summary' => FALSE, + ] + parent::defaultFieldSettings(); + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties = parent::propertyDefinitions($field_definition); + + $properties['summary'] = DataDefinition::create('string') + ->setLabel(new TranslatableMarkup('Summary')); + + $properties['summary_processed'] = DataDefinition::create('string') + ->setLabel(new TranslatableMarkup('Processed summary')) + ->setDescription(new TranslatableMarkup('The summary text with the text format applied.')) + ->setComputed(TRUE) + ->setClass('\Drupal\text\TextProcessed') + ->setSetting('text source', 'summary'); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return [ + 'columns' => [ + 'value' => [ + 'type' => 'text', + 'size' => 'big', + ], + 'summary' => [ + 'type' => 'text', + 'size' => 'big', + ], + 'format' => [ + 'type' => 'varchar_ascii', + 'length' => 255, + ], + ], + 'indexes' => [ + 'format' => ['format'], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->get('summary')->getValue(); + return parent::isEmpty() && ($value === NULL || $value === ''); + } + + /** + * {@inheritdoc} + */ + public function fieldSettingsForm(array $form, FormStateInterface $form_state) { + $element = []; + $settings = $this->getSettings(); + + $element['display_summary'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Summary input'), + '#default_value' => $settings['display_summary'], + '#description' => $this->t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'), + ]; + + $element['required_summary'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Require summary'), + '#description' => $this->t('The summary will also be visible when marked as required.'), + '#default_value' => $settings['required_summary'], + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + $constraints = parent::getConstraints(); + if ($this->getSetting('required_summary')) { + $manager = $this->getTypedDataManager()->getValidationConstraintManager(); + $constraints[] = $manager->create('ComplexData', [ + 'summary' => [ + 'NotNull' => [ + 'message' => $this->t('The summary field is required for @name', ['@name' => $this->getFieldDefinition()->getLabel()]), + ], + ], + ]); + } + return $constraints; + } + +} diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php index fc02ac1e9c..bebc8ab55a 100644 --- a/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php +++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php @@ -34,11 +34,17 @@ public function settingsForm(array $form, FormStateInterface $form_state) { */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $main_widget = parent::formElement($items, $delta, $element, $form, $form_state); + $allowed_formats = $this->getFieldSetting('allowed_formats'); $element = $main_widget['value']; $element['#type'] = 'text_format'; $element['#format'] = $items[$delta]->format; $element['#base_type'] = $main_widget['value']['#type']; + + if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) { + $element['#allowed_formats'] = $allowed_formats; + } + return $element; } diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php index 29934378f0..8942ec338d 100644 --- a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php +++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php @@ -25,11 +25,17 @@ class TextfieldWidget extends StringTextfieldWidget { */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $main_widget = parent::formElement($items, $delta, $element, $form, $form_state); + $allowed_formats = $this->getFieldSetting('allowed_formats'); $element = $main_widget['value']; $element['#type'] = 'text_format'; $element['#format'] = $items[$delta]->format ?? NULL; $element['#base_type'] = $main_widget['value']['#type']; + + if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) { + $element['#allowed_formats'] = $allowed_formats; + } + return $element; } diff --git a/core/modules/text/tests/src/Functional/TextFieldTest.php b/core/modules/text/tests/src/Functional/TextFieldTest.php index b68c0d1f37..54b0b87c3f 100644 --- a/core/modules/text/tests/src/Functional/TextFieldTest.php +++ b/core/modules/text/tests/src/Functional/TextFieldTest.php @@ -21,6 +21,13 @@ class TextFieldTest extends StringFieldTest { getTestFiles as drupalGetTestFiles; } + /** + * Modules to enable. + * + * @var array + */ + protected static $modules = ['entity_test', 'file', 'field_ui']; + /** * A user with relevant administrative privileges. * @@ -39,7 +46,10 @@ class TextFieldTest extends StringFieldTest { protected function setUp(): void { parent::setUp(); - $this->adminUser = $this->drupalCreateUser(['administer filters']); + $this->adminUser = $this->drupalCreateUser([ + 'administer filters', + 'administer entity_test fields', + ]); } // Test fields. @@ -157,13 +167,94 @@ public function testTextfieldWidgetsFormatted() { $this->_testTextfieldWidgetsFormatted('text_long', 'text_textarea'); } + /** + * Test widgets for fields with selected allowed formats. + */ + public function testTextfieldWidgetsAllowedFormats() { + // Create one text format. + $this->drupalLogin($this->adminUser); + $format1 = FilterFormat::create([ + 'format' => mb_strtolower($this->randomMachineName()), + 'name' => $this->randomMachineName(), + ]); + $format1->save(); + + // Create a second text format. + $format2 = FilterFormat::create([ + 'format' => mb_strtolower($this->randomMachineName()), + 'name' => $this->randomMachineName(), + ]); + $format2->save(); + + // Grant access to both formats to the user. + $roles = $this->webUser->getRoles(); + $rid = $roles[0]; + user_role_grant_permissions($rid, [ + $format1->getPermissionName(), + $format2->getPermissionName(), + ]); + + // Create a field with multiple formats allowed. + $field_name = mb_strtolower($this->randomMachineName()); + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'text', + ]); + $field_storage->save(); + $field = FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'entity_test', + 'label' => $this->randomMachineName() . '_label', + 'settings' => ['allowed_formats' => []], + ]); + $field->save(); + + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository */ + $entity_display_repository = $this->container->get('entity_display.repository'); + $entity_display_repository->getFormDisplay('entity_test', 'entity_test', 'default') + ->setComponent($field_name, [ + 'type' => 'text_textfield', + ]) + ->save(); + $entity_display_repository->getViewDisplay('entity_test', 'entity_test', 'full') + ->setComponent($field_name) + ->save(); + + // Display the creation form. + $this->drupalLogin($this->webUser); + $this->drupalGet('entity_test/add'); + $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL); + $this->assertSession()->optionExists("{$field_name}[0][format]", $format1->id()); + $this->assertSession()->optionExists("{$field_name}[0][format]", $format2->id()); + // Log back in as admin. + $this->drupalLogin($this->adminUser); + // Change field to allow only one format. + $path = "entity_test/structure/entity_test/fields/entity_test.entity_test.$field_name"; + $this->drupalGet($path); + $this->submitForm(["settings[allowed_formats][{$format1->id()}]" => $format1->id()], 'Save settings'); + $this->drupalGet($path); + // Display the creation form. + $this->drupalLogin($this->webUser); + // We shouldn't have the 'format' selector since only one format is allowed. + $this->drupalGet('entity_test/add'); + $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL); + $this->assertSession()->fieldNotExists("{$field_name}[0][format]"); + + // Change field to allow all formats by configuring none as allowed. + $field->setSetting('allowed_formats', []); + $field->save(); + $this->drupalGet('entity_test/add'); + // We should see the 'format' selector again. + $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL); + $this->assertSession()->optionExists("{$field_name}[0][format]", $format1->id()); + $this->assertSession()->optionExists("{$field_name}[0][format]", $format2->id()); + } + /** * Helper function for testTextfieldWidgetsFormatted(). */ public function _testTextfieldWidgetsFormatted($field_type, $widget_type) { - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = $this->container->get('renderer'); - // Create a field. $field_name = mb_strtolower($this->randomMachineName()); $field_storage = FieldStorageConfig::create([ diff --git a/core/modules/text/tests/src/Kernel/TextItemBaseTest.php b/core/modules/text/tests/src/Kernel/TextItemBaseTest.php index 50abbc2612..3087cb7fa3 100644 --- a/core/modules/text/tests/src/Kernel/TextItemBaseTest.php +++ b/core/modules/text/tests/src/Kernel/TextItemBaseTest.php @@ -3,6 +3,9 @@ namespace Drupal\Tests\text\Kernel; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\filter\Entity\FilterFormat; use Drupal\KernelTests\KernelTestBase; use Drupal\text\Plugin\Field\FieldType\TextItemBase; @@ -16,7 +19,7 @@ class TextItemBaseTest extends KernelTestBase { /** * {@inheritdoc} */ - protected static $modules = ['filter', 'text']; + protected static $modules = ['filter', 'text', 'entity_test', 'field']; /** * Tests creation of sample values. @@ -57,4 +60,42 @@ public function providerTextFieldSampleValue() { ]; } + /** + * @covers ::calculateDependencies + */ + public function testCalculateDependencies() { + $format = FilterFormat::create([ + 'format' => 'test_format', + 'name' => 'Test format', + ]); + $fieldName = mb_strtolower($this->randomMachineName()); + $field_storage = FieldStorageConfig::create([ + 'field_name' => $fieldName, + 'entity_type' => 'entity_test', + 'type' => 'text', + ]); + $field_storage->save(); + $field = FieldConfig::create([ + 'field_name' => $fieldName, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'settings' => [ + 'allowed_formats' => [$format->id()], + ], + ]); + $field->save(); + + $field->calculateDependencies(); + $this->assertEquals([ + 'module' => [ + 'entity_test', + 'text', + ], + 'config' => [ + "field.storage.entity_test.$fieldName", + 'filter.format.test_format', + ], + ], $field->getDependencies()); + } + } diff --git a/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php b/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php index 6630330bdb..9559f03eea 100644 --- a/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php +++ b/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php @@ -67,7 +67,7 @@ public function testCrudAndUpdate() { $entity = $storage->create(); $entity->summary_field->value = $value = $this->randomMachineName(); $entity->summary_field->summary = $summary = $this->randomMachineName(); - $entity->summary_field->format = NULL; + $entity->summary_field->format = 'plain_text'; $entity->name->value = $this->randomMachineName(); $entity->save(); @@ -76,7 +76,7 @@ public function testCrudAndUpdate() { $this->assertInstanceOf(FieldItemInterface::class, $entity->summary_field[0]); $this->assertEquals($value, $entity->summary_field->value); $this->assertEquals($summary, $entity->summary_field->summary); - $this->assertNull($entity->summary_field->format); + $this->assertEquals('plain_text', $entity->summary_field->format); // Even if no format is given, if text processing is enabled, the default // format is used. $this->assertEquals("

{$value}

\n", $entity->summary_field->processed); @@ -115,6 +115,9 @@ protected function createField($entity_type) { $this->field = FieldConfig::create([ 'field_storage' => $this->fieldStorage, 'bundle' => $entity_type, + 'settings' => [ + 'allowed_formats' => ['plain_text'], + ], ]); $this->field->save(); } diff --git a/core/modules/text/text.post_update.php b/core/modules/text/text.post_update.php index 25322eb977..bcae897684 100644 --- a/core/modules/text/text.post_update.php +++ b/core/modules/text/text.post_update.php @@ -8,6 +8,8 @@ use Drupal\Core\Config\Entity\ConfigEntityUpdater; use Drupal\Core\Entity\Display\EntityDisplayInterface; use Drupal\text\Plugin\Field\FieldWidget\TextareaWithSummaryWidget; +use Drupal\text\Plugin\Field\FieldType\TextItemBase; +use Drupal\field\FieldConfigInterface; /** * Implements hook_removed_post_updates(). @@ -46,3 +48,24 @@ function text_post_update_add_required_summary_flag_form_display(&$sandbox = NUL $config_entity_updater->update($sandbox, 'entity_form_display', $widget_callback); } + +/** + * Add allowed_formats setting to existing text fields. + */ +function test_post_update_allowed_formats(&$sandbox = NULL) { + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'field_config', function (FieldConfigInterface $field_config) { + $class = get_class($field_config); + // Deal only with text fields and descendants. + if ($class == TextItemBase::class || is_subclass_of($class, TextItemBase::class)) { + // Get the existing allowed_formats setting. + $allowed_formats = $field_config->get('settings.allowed_formats'); + if (!is_array($allowed_formats) && empty($allowed_formats)) { + // Save default value if existing value not present. + $field_config->set('settings.allowed_formats', []); + } + return TRUE; + } + return FALSE; + }); +} diff --git a/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php b/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php index 8bf1de1183..dd248e2174 100644 --- a/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php +++ b/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php @@ -80,7 +80,10 @@ protected function setUp($import_test_views = TRUE): void { 'field_storage' => FieldStorageConfig::loadByName('node', 'body'), 'bundle' => $content_type->id(), 'label' => $this->randomMachineName() . '_body', - 'settings' => ['display_summary' => TRUE], + 'settings' => [ + 'display_summary' => TRUE, + 'allowed_formats' => [], + ], ])->save(); ViewTestData::createTestViews(static::class, ['views_test_config']); diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml b/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml index dab4f98181..40968daa4d 100644 --- a/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml +++ b/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: false required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml index 964a88887c..30cbbdbb05 100644 --- a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml +++ b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml @@ -16,5 +16,6 @@ required: false translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml index d2d3c91850..1aab290261 100644 --- a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml +++ b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml @@ -16,5 +16,6 @@ required: false translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/profiles/demo_umami/config/install/field.field.node.article.body.yml b/core/profiles/demo_umami/config/install/field.field.node.article.body.yml index b36fbd5844..66f00ac4a5 100644 --- a/core/profiles/demo_umami/config/install/field.field.node.article.body.yml +++ b/core/profiles/demo_umami/config/install/field.field.node.article.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/profiles/demo_umami/config/install/field.field.node.page.body.yml b/core/profiles/demo_umami/config/install/field.field.node.page.body.yml index 4ff17d0e71..c81d7034f3 100644 --- a/core/profiles/demo_umami/config/install/field.field.node.page.body.yml +++ b/core/profiles/demo_umami/config/install/field.field.node.page.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml index ff090cac35..788f56d189 100644 --- a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml +++ b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml @@ -16,5 +16,6 @@ required: true translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml index b648b97889..7ea393bc51 100644 --- a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml +++ b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml @@ -16,5 +16,6 @@ required: true translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/profiles/standard/config/install/field.field.block_content.basic.body.yml b/core/profiles/standard/config/install/field.field.block_content.basic.body.yml index dab4f98181..40968daa4d 100644 --- a/core/profiles/standard/config/install/field.field.block_content.basic.body.yml +++ b/core/profiles/standard/config/install/field.field.block_content.basic.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: false required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml b/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml index 1337070d16..8d97e03507 100644 --- a/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml +++ b/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml @@ -16,5 +16,6 @@ required: true translatable: true default_value: { } default_value_callback: '' -settings: { } +settings: + allowed_formats: { } field_type: text_long diff --git a/core/profiles/standard/config/install/field.field.node.article.body.yml b/core/profiles/standard/config/install/field.field.node.article.body.yml index b36fbd5844..66f00ac4a5 100644 --- a/core/profiles/standard/config/install/field.field.node.article.body.yml +++ b/core/profiles/standard/config/install/field.field.node.article.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary diff --git a/core/profiles/standard/config/install/field.field.node.page.body.yml b/core/profiles/standard/config/install/field.field.node.page.body.yml index 4ff17d0e71..c81d7034f3 100644 --- a/core/profiles/standard/config/install/field.field.node.page.body.yml +++ b/core/profiles/standard/config/install/field.field.node.page.body.yml @@ -19,4 +19,5 @@ default_value_callback: '' settings: display_summary: true required_summary: false + allowed_formats: { } field_type: text_with_summary