diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php index b9af2323fc6f01185f8097ea8822f36c8dca3e1f..7a200baf86dc532d6b3a06b99e4a2584e169a093 100644 --- a/core/lib/Drupal/Core/Form/ConfigFormBase.php +++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php @@ -137,7 +137,6 @@ public function storeConfigKeyToFormElementMap(array $element, FormStateInterfac if (is_string($target)) { $target = ConfigTarget::fromString($target); } - $target->elementName = $element['#name']; $target->elementParents = $element['#parents']; $map[$target->configName . ':' . $target->propertyPath] = $target; $form_state->set(static::CONFIG_KEY_TO_FORM_ELEMENT_MAP, $map); @@ -192,7 +191,20 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // will not have the sequence index in it. $property_path = rtrim($property_path, '0123456789.'); } - $form_element_name = $map["$config_name:$property_path"]->elementName; + + if ($property_path === '') { + // There is a map to a non-existing config key. Try to work backwards. + $property_path = $violation->getParameters()['@key'] ?? ''; + } + + if (isset($map["$config_name:$property_path"])) { + $form_element_name = implode('][', $map["$config_name:$property_path"]->elementParents); + } + else { + // We cannot determine where to place the violation. The only option + // is the entire form. + $form_element_name = ''; + } $violations_per_form_element[$form_element_name][$index] = $violation; } diff --git a/core/lib/Drupal/Core/Form/ConfigTarget.php b/core/lib/Drupal/Core/Form/ConfigTarget.php index 45eee5799dfa47c1724674ceac2f55a5fc3f49ef..ddf7bc50007191181ddea394c393089d31b928ab 100644 --- a/core/lib/Drupal/Core/Form/ConfigTarget.php +++ b/core/lib/Drupal/Core/Form/ConfigTarget.php @@ -9,18 +9,6 @@ */ final class ConfigTarget { - /** - * The name of the form element which maps to this config property. - * - * @var string - * - * @see \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap() - * - * @internal - * This property is for internal use only. - */ - public string $elementName; - /** * The parents of the form element which maps to this config property. * diff --git a/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml b/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml index 9af17cdd860c35fc16ae8ab9b65973d92dd2ced3..dccdc5bc93636e9d7cff4dbdfd82a8b8bcb8e5cc 100644 --- a/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml +++ b/core/modules/system/tests/modules/form_test/config/schema/form_test.schema.yml @@ -5,3 +5,9 @@ form_test.object: bananas: type: string label: 'Bananas' + favorite_vegetable: + type: required_label + label: 'Favorite vegetable' + nemesis_vegetable: + type: required_label + label: 'Nemesis vegetable' diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index 9c971a727e1dd81faaa268e7f6e515ca552e247d..fd4db9da386f76e549ff1100db996c8f532c3963 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -528,3 +528,17 @@ form_test.javascript_states_form: _form: '\Drupal\form_test\Form\JavascriptStatesForm' requirements: _access: 'TRUE' + +form_test.tree_config_target: + path: '/form-test/tree-config-target' + defaults: + _form: '\Drupal\form_test\Form\TreeConfigTargetForm' + requirements: + _access: 'TRUE' + +form_test.incorrect_config_target: + path: '/form-test/incorrect-config-target' + defaults: + _form: '\Drupal\form_test\Form\IncorrectConfigTargetForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php b/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php new file mode 100644 index 0000000000000000000000000000000000000000..5296edb9cb88ef98bb17b90618d624b1194df484 --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/IncorrectConfigTargetForm.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\form_test\Form; + +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; + +class IncorrectConfigTargetForm extends ConfigFormBase { + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return ['form_test.object']; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'form_test_incorrect_config_target_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['missing_key'] = [ + '#type' => 'textfield', + '#title' => t('Missing key'), + '#config_target' => 'form_test.object:does_not_exist', + ]; + return parent::buildForm($form, $form_state); + } + +} diff --git a/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php new file mode 100644 index 0000000000000000000000000000000000000000..502fbd8f71300d990e6733dcc5c93049c06df119 --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php @@ -0,0 +1,49 @@ +<?php + +namespace Drupal\form_test\Form; + +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; + +class TreeConfigTargetForm extends ConfigFormBase { + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return ['form_test.object']; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'form_test_tree_config_target_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['vegetables'] = [ + '#type' => 'details', + '#tree' => TRUE, + '#input' => TRUE, + '#title' => t('Vegetable preferences'), + ]; + $form['vegetables']['favorite'] = [ + '#type' => 'textfield', + '#title' => t('Favorite'), + '#default_value' => 'Potato', + '#config_target' => 'form_test.object:favorite_vegetable', + ]; + $form['vegetables']['nemesis'] = [ + '#type' => 'textfield', + '#title' => t('Nemesis'), + '#default_value' => 'Broccoli', + '#config_target' => 'form_test.object:nemesis_vegetable', + ]; + return parent::buildForm($form, $form_state); + } + +} diff --git a/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5ab7a735956fc45e81f208bdb492ffb28ffb518a --- /dev/null +++ b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\system\Functional\Form; + +use Drupal\Tests\BrowserTestBase; + +/** + * Tests forms using #config_target. + * + * @group Form + */ +class ConfigTargetTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['form_test']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Tests #config_target where #tree is set to TRUE. + */ + public function testTree(): void { + $this->drupalGet('/form-test/tree-config-target'); + $page = $this->getSession()->getPage(); + $page->fillField('Favorite', ''); + $page->pressButton('Save configuration'); + $assert_session = $this->assertSession(); + $assert_session->statusMessageContains('This value should not be blank.', 'error'); + $assert_session->elementAttributeExists('named', ['field', 'Favorite'], 'aria-invalid'); + $assert_session->elementAttributeNotExists('named', ['field', 'Nemesis'], 'aria-invalid'); + } + + /** + * Tests #config_target with an incorrect key. + */ + public function testIncorrectKey(): void { + $this->drupalGet('/form-test/incorrect-config-target'); + $page = $this->getSession()->getPage(); + $page->pressButton('Save configuration'); + $assert_session = $this->assertSession(); + $assert_session->statusMessageContains('\'does_not_exist\' is not a supported key.', 'error'); + $assert_session->elementAttributeExists('named', ['field', 'Missing key'], 'aria-invalid'); + } + +}