diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php index 1319dc3..d0aadf1 100644 --- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php @@ -218,12 +218,24 @@ 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) { - $element['#element_validate'][] = [get_called_class(), 'validateTitleElement']; + if (!$this->isDefaultValueWidget($form_state)) { + if ($this->getFieldSetting('title') == DRUPAL_REQUIRED) { + $element['#element_validate'][] = [get_called_class(), 'validateTitleElement']; + + // Make title required on the front-end when uri filled-in. + if (!$element['title']['#required']) { + $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..ecfadcd 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. @@ -737,4 +738,128 @@ 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->assertEqual(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->assertEqual(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->assertEqual(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->assertEqual(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->assertEqual(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->assertEqual(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, t('Save')); + $this->assertNoText(t('@name field is required.', array('@name' => t('Link text')))); + + // 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, t('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->assertEqual(count($result), 1, "URL is not marked as required when only link text is set as required."); + $result = $this->xpath('//label[contains(@class, :class) and contains(@for, :for)]', [':class' => 'form-required', ':for' => 'edit-' . $field_text_only . '-0-title']); + $this->assertEqual(count($result), 1, "Link text is marked as required when only the link text is set to required."); + } + }