diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php index 1319dc3..7faaa15 100644 --- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php @@ -151,7 +151,6 @@ public static function validateUriElement($element, FormStateInterface $form_sta */ public static function validateTitleElement(&$element, FormStateInterface $form_state, $form) { if ($element['uri']['#value'] !== '' && $element['title']['#value'] === '') { - $element['title']['#required'] = TRUE; // We expect the field name placeholder value to be wrapped in t() here, // so it won't be escaped again as it's already marked safe. $form_state->setError($element['title'], t('@name field is required.', ['@name' => $element['title']['#title']])); @@ -218,12 +217,22 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL, '#maxlength' => 255, '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED, + '#required' => $this->getFieldSetting('title') === DRUPAL_REQUIRED && $element['#required'], ]; + // Post-process the title field to make it conditionally required if URL is // non-empty. Omit the validation on the field edit form, since the field // settings cannot be saved otherwise. - if (!$this->isDefaultValueWidget($form_state) && $this->getFieldSetting('title') == DRUPAL_REQUIRED) { + if (!$this->isDefaultValueWidget($form_state) && $this->getFieldSetting('title') === DRUPAL_REQUIRED) { $element['#element_validate'][] = [get_called_class(), 'validateTitleElement']; + + if (!$element['title']['#required']) { + // Make title required on the front end when URI filled-in. + $field_name = $this->fieldDefinition->get('field_name'); + $element['title']['#states']['required'] = [ + ':input[name="' . $field_name . '[' . $delta . '][uri]"]' => ['filled' => TRUE] + ]; + } } // Exposing the attributes array in the widget is left for alternate and more diff --git a/core/modules/link/tests/src/Functional/LinkFieldTest.php b/core/modules/link/tests/src/Functional/LinkFieldTest.php index c99ad0a..1efde54 100644 --- a/core/modules/link/tests/src/Functional/LinkFieldTest.php +++ b/core/modules/link/tests/src/Functional/LinkFieldTest.php @@ -11,6 +11,7 @@ use Drupal\node\NodeInterface; use Drupal\Tests\BrowserTestBase; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\Core\Entity\Entity\EntityFormDisplay; /** * Tests link field widgets and formatters. @@ -282,14 +283,14 @@ public function testLinkTitle() { "{$field_name}[0][uri]" => 'http://www.example.com', ]; $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertText(t('@name field is required.', ['@name' => t('Link text')])); + $this->assertText('Link text field is required'); // Verify that the link text is not required, if the URL is empty. $edit = [ "{$field_name}[0][uri]" => '', ]; $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')])); + $this->assertSession()->pageTextNotContains('Link text field is required'); // Verify that a URL and link text meets requirements. $this->drupalGet('entity_test/add'); @@ -298,7 +299,7 @@ public function testLinkTitle() { "{$field_name}[0][title]" => 'Example', ]; $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')])); + $this->assertSession()->pageTextNotContains('Link text field is required'); } } } @@ -737,4 +738,144 @@ protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) { return $output; } + /** + * Tests for the URL and/or link text are marked as required. + * + * Three tests are done with different field configurations: + * - Tests for both URL and the link text are both marked as required. + * - Tests for URL marked as required, but the link text is not. + * - Tests for URL not marked as required, but the link text is marked. + */ + function testLinkRequired() { + $field_both_required = Unicode::strtolower($this->randomMachineName()); + $field_url_only = Unicode::strtolower($this->randomMachineName()); + $field_text_only = Unicode::strtolower($this->randomMachineName()); + + foreach ([$field_both_required, $field_url_only, $field_text_only] as $field_name) { + FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'link', + ])->save(); + } + + // Creates a field with both URL and link text set as required. + FieldConfig::create([ + 'field_name' => $field_both_required, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'label' => 'Read more about this entity', + 'required' => TRUE, + 'settings' => [ + 'title' => DRUPAL_REQUIRED, + 'link_type' => LinkItemInterface::LINK_GENERIC, + ], + ])->save(); + + // Creates a field with only URL set as required. + FieldConfig::create([ + 'field_name' => $field_url_only, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'label' => 'Read more about this entity', + 'required' => TRUE, + 'settings' => [ + 'title' => DRUPAL_OPTIONAL, + 'link_type' => LinkItemInterface::LINK_GENERIC, + ], + ])->save(); + + // Creates a field with only link text set as required. + FieldConfig::create([ + 'field_name' => $field_text_only, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'label' => 'Read more about this entity', + 'required' => FALSE, + 'settings' => [ + 'title' => DRUPAL_REQUIRED, + 'link_type' => LinkItemInterface::LINK_GENERIC, + ], + ])->save(); + + // Sets the form display. + $component_settings = [ + 'type' => 'link_default', + 'settings' => [ + 'placeholder_url' => 'http://example.com', + 'placeholder_title' => 'Enter the text for this link', + ], + ]; + EntityFormDisplay::load('entity_test.entity_test.default') + ->setComponent($field_both_required, $component_settings) + ->setComponent($field_url_only, $component_settings) + ->setComponent($field_text_only, $component_settings) + ->save(); + + // Display creation form. + $this->drupalGet('entity_test/add'); + + // Tests if both URL and link text are both marked as required for the field + // $field_both_required. + $result = $this->xpath('//label[contains(@class, :class) and contains(text(), :text) and contains(@for, :for)]', [':class' => 'form-required', ':text' => 'URL', ':for' => 'edit-' . $field_both_required . '-0-uri']); + $this->assertEquals(count($result), 1, "URL is marked as required when both the URL and link field are set to required."); + $result = $this->xpath('//label[contains(@class, :class) and contains(text(), :text) and contains(@for, :for)]', [':class' => 'form-required', ':text' => 'Link text', ':for' => 'edit-' . $field_both_required . '-0-title']); + $this->assertEquals(count($result), 1, "Link text is marked as required when both the URL and link field are set to required."); + + // Tests if only link text marked as required for the field $field_url_only. + $result = $this->xpath('//label[contains(@class, :class) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_url_only . '-0-uri']); + $this->assertEquals(count($result), 1, "URL is marked as required when only the URL is set to required."); + $result = $this->xpath('//label[not(contains(@class, :class)) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_url_only . '-0-title']); + $this->assertEquals(count($result), 1, "Link text is not marked as required when only the URL is set to required."); + + // Tests if URL or or link text are not marked as required for + // $field_text_only before posting the form. + $result = $this->xpath('//label[not(contains(@class, :class)) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_text_only . '-0-uri']); + $this->assertEquals(count($result), 1, "Before posting the form the URL is not marked as required when only link text is set as required."); + $result = $this->xpath('//label[not(contains(@class, :class)) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_text_only . '-0-title']); + $this->assertEquals(count($result), 1, "Before posting the form the link text is not marked as required when only the link text is set to required."); + + // Tests if the link text is not marked as required after posting a form + // with data only for URL for the field $field_url_only. + $edit = [ + "{$field_both_required}[0][uri]" => 'http://www.example.com', + "{$field_both_required}[0][title]" => 'http://www.example.com', + "{$field_url_only}[0][uri]" => 'http://www.example.com', + ]; + $this->drupalPostForm('entity_test/add', $edit, 'Save'); + $this->assertSession()->pageTextNotContains('Link text field is required.'); + + // Posts all required data, except for the link text for $field_text_only. + $edit = [ + "{$field_both_required}[0][uri]" => 'http://www.example.com', + "{$field_both_required}[0][title]" => 'http://www.example.com', + "{$field_url_only}[0][uri]" => 'http://www.example.com', + "{$field_text_only}[0][uri]" => 'http://www.example.com', + ]; + $this->drupalPostForm('entity_test/add', $edit, 'Save'); + + // Tests if only link text is marked as required for $field_text_only after + // posting a form with URL, but without link text. + $result = $this->xpath('//label[not(contains(@class, :class)) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_text_only . '-0-uri']); + $this->assertEquals(count($result), 1, "URL is not marked as required when only link text is set as required."); + $result = $this->xpath('//label[not(contains(@class, :class)) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_text_only . '-0-title']); + $this->assertEquals(count($result), 1, "Link text is not marked as required when only the link text is set to required."); + $this->assertSession()->pageTextContains('Link text field is required.'); + + // Posts all required data. + $edit = [ + "{$field_both_required}[0][uri]" => 'http://www.example.com', + "{$field_both_required}[0][title]" => 'http://www.example.com', + "{$field_url_only}[0][uri]" => 'http://www.example.com', + "{$field_text_only}[0][uri]" => 'http://www.example.com', + "{$field_text_only}[0][title]" => 'http://www.example.com', + ]; + $this->drupalPostForm('entity_test/add', $edit, 'Save'); + // Tests if the entity is created correctly. + preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match); + $id = $match[1]; + $this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.'); + + } + }