diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
index 25222b78976..2dc8ed67600 100644
--- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
+++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
@@ -257,6 +257,29 @@ class LinkWidget extends WidgetBase {
// title of the 'uri' element.
if ($this->getFieldSetting('title') == DRUPAL_DISABLED) {
$element['uri']['#title'] = $element['#title'];
+ // By default the field description is added to the title field. Since
+ // the title field is disabled, we add the description, if given, to the
+ // uri element instead.
+ if (!empty($element['#description'])) {
+ if (empty($element['uri']['#description'])) {
+ $element['uri']['#description'] = $element['#description'];
+ }
+ else {
+ // If we have the description of the type of field together with
+ // the user provided description, we want to make a distinction
+ // between "core help text" and "user entered help text". To make
+ // this distinction more clear, we put them in an unordered list.
+ $element['uri']['#description'] = [
+ '#theme' => 'item_list',
+ '#items' => [
+ // Assume the user-specified description has the most relevance,
+ // so place it first.
+ $element['#description'],
+ $element['uri']['#description'],
+ ],
+ ];
+ }
+ }
}
// Otherwise wrap everything in a details element.
else {
diff --git a/core/modules/link/tests/src/Functional/LinkFieldUITest.php b/core/modules/link/tests/src/Functional/LinkFieldUITest.php
index 0da393834b7..bd2433f903d 100644
--- a/core/modules/link/tests/src/Functional/LinkFieldUITest.php
+++ b/core/modules/link/tests/src/Functional/LinkFieldUITest.php
@@ -2,7 +2,10 @@
namespace Drupal\Tests\link\Functional;
-use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field_ui\Tests\FieldUiTestTrait;
use Drupal\link\LinkItemInterface;
use Drupal\Tests\BrowserTestBase;
@@ -30,14 +33,37 @@ class LinkFieldUITest extends BrowserTestBase {
*/
protected $adminUser;
+ /**
+ * A user that should see the help texts.
+ *
+ * @var \Drupal\user\Entity\User
+ */
+ protected $helpTextUser;
+
+ /**
+ * The first content type to add fields to.
+ *
+ * @var \Drupal\node\Entity\NodeType
+ */
+ protected $firstContentType;
+
+ /**
+ * The second content type to add fields to.
+ *
+ * @var \Drupal\node\Entity\NodeType
+ */
+ protected $secondContentType;
+
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
+ $this->firstContentType = $this->drupalCreateContentType();
+ $this->secondContentType = $this->drupalCreateContentType();
$this->adminUser = $this->drupalCreateUser(['administer content types', 'administer node fields', 'administer node display']);
- $this->drupalLogin($this->adminUser);
+ $this->helpTextUser = $this->drupalCreateUser(['create ' . $this->secondContentType->id() . ' content']);
$this->drupalPlaceBlock('system_breadcrumb_block');
}
@@ -45,16 +71,91 @@ class LinkFieldUITest extends BrowserTestBase {
* Tests the link field UI.
*/
public function testFieldUI() {
- // Add a content type.
- $type = $this->drupalCreateContentType();
- $type_path = 'admin/structure/types/manage/' . $type->id();
- $add_path = 'node/add/' . $type->id();
+ foreach ($this->providerTestFieldUI() as $item) {
+ list($cardinality, $link_type, $title, $label, $field_name) = $item;
+ $this->runFieldUIItem($cardinality, $link_type, $title, $label, $field_name);
+ }
+ }
+
+ /**
+ * Provides test data for ::testFieldUI().
+ */
+ protected function providerTestFieldUI() {
+ // There are many combinations of field settings: where the description
+ // should show: variation on internal, external, both; cardinality (where
+ // the fieldset is hidden or used); and link text shown (required or
+ // optional) or disabled. There are two descriptions: field and URL help
+ // text.
+ $cardinalities = [1, 2];
+ $title_settings = [
+ DRUPAL_DISABLED,
+ DRUPAL_OPTIONAL,
+ ];
+ $link_types = [
+ LinkItemInterface::LINK_EXTERNAL,
+ LinkItemInterface::LINK_GENERIC,
+ LinkItemInterface::LINK_INTERNAL,
+ ];
+
+ // Test all variations of link types on all cardinalities.
+ foreach ($cardinalities as $cardinality) {
+ foreach ($link_types as $link_type) {
+ // Now, test this with both the title enabled and disabled.
+ foreach ($title_settings as $title_setting) {
+ // Test both empty and non-empty labels.
+ foreach ([TRUE, FALSE] as $label_provided) {
+ // Generate a unique machine name for the field so it can be
+ // identified in the test.
+ $id = implode('_', [
+ 'link',
+ $cardinality,
+ $link_type,
+ $title_setting,
+ (int) $label_provided,
+ ]);
+
+ // Use a unique label that contains some HTML.
+ $label = '
' . $id;
+
+ yield [
+ $cardinality,
+ $link_type,
+ $title_setting,
+ $label_provided ? $label : '',
+ $id,
+ ];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests one link field UI item.
+ *
+ * @param int $cardinality
+ * The field cardinality.
+ * @param int $link_type
+ * Determine if the link is external, internal or both.
+ * @param int $title
+ * Determine if the field will display the link text field.
+ * @param string $label
+ * The field label.
+ * @param string $field_name
+ * The unique machine name for the field.
+ */
+ public function runFieldUIItem($cardinality, $link_type, $title, $label, $field_name) {
+ $this->drupalLogin($this->adminUser);
+ $type_path = 'admin/structure/types/manage/' . $this->firstContentType->id();
// Add a link field to the newly-created type. It defaults to allowing both
// internal and external links.
- $label = $this->randomMachineName();
- $field_name = Unicode::strtolower($label);
- $this->fieldUIAddNewField($type_path, $field_name, $label, 'link');
+ $field_label = str_replace('_', ' ', $field_name);
+ $description = 'link field description';
+ $field_edit = [
+ 'description' => $description,
+ ];
+ $this->fieldUIAddNewField($type_path, $field_name, $field_label, 'link', [], $field_edit);
// Load the formatter page to check that the settings summary does not
// generate warnings.
@@ -62,31 +163,94 @@ class LinkFieldUITest extends BrowserTestBase {
$this->drupalGet("$type_path/display");
$this->assertText(t('Link text trimmed to @limit characters', ['@limit' => 80]));
- // Test the help text displays when the link field allows both internal and
- // external links.
- $this->drupalLogin($this->drupalCreateUser(['create ' . $type->id() . ' content']));
+ $storage = FieldStorageConfig::create([
+ 'field_name' => $field_name,
+ 'entity_type' => 'node',
+ 'type' => 'link',
+ 'cardinality' => $cardinality,
+ ]);
+ $storage->save();
+
+ FieldConfig::create([
+ 'field_storage' => $storage,
+ 'label' => $label,
+ 'bundle' => $this->secondContentType->id(),
+ 'settings' => [
+ 'title' => $title,
+ 'link_type' => $link_type,
+ ],
+ ])->save();
+
+ // Make the fields visible in the form display.
+ $form_display_id = implode('.', ['node', $this->secondContentType->id(), 'default']);
+ $form_display = EntityFormDisplay::load($form_display_id);
+ $form_display->setComponent($field_name, ['region' => 'content']);
+ $form_display->save();
+
+ // Log in a user that is allowed to create this content type, see if
+ // the user can see the expected help text.
+ $this->drupalLogin($this->helpTextUser);
+
+ $add_path = 'node/add/' . $this->secondContentType->id();
$this->drupalGet($add_path);
- $this->assertRaw('You can also enter an internal path such as /node/add or an external URL such as http://example.com.');
- // Log in an admin to set up the next content type.
- $this->drupalLogin($this->adminUser);
+ $expected_help_texts = [
+ LinkItemInterface::LINK_EXTERNAL => 'This must be an external URL such as http://example.com.',
+ LinkItemInterface::LINK_GENERIC => 'You can also enter an internal path such as /node/add or an external URL such as http://example.com. Enter <front> to link to the front page.',
+ LinkItemInterface::LINK_INTERNAL => rtrim(\Drupal::url('', [], ['absolute' => TRUE]), '/'),
+ ];
- // Add a different content type.
- $type = $this->drupalCreateContentType();
- $type_path = 'admin/structure/types/manage/' . $type->id();
- $add_path = 'node/add/' . $type->id();
+ // Check that the help texts we assume should be there, is there.
+ $this->assertFieldContainsRawText($field_name, $expected_help_texts[$link_type]);
+ if ($link_type === LinkItemInterface::LINK_INTERNAL) {
+ // Internal links have no "system" description. Test that none
+ // of the other help texts show here.
+ $this->assertNoFieldContainsRawText($field_name, $expected_help_texts[LinkItemInterface::LINK_EXTERNAL]);
+ $this->assertNoFieldContainsRawText($field_name, $expected_help_texts[LinkItemInterface::LINK_GENERIC]);
+ }
+ // Also assert that the description we made is here, no matter what the
+ // cardinality or link setting.
+ if (!empty($label)) {
+ $this->assertFieldContainsRawText($field_name, $label);
+ }
+ }
- // Add a link field to the newly-created type. Specify it must allow
- // external only links.
- $label = $this->randomMachineName();
- $field_name = Unicode::strtolower($label);
- $field_edit = ['settings[link_type]' => LinkItemInterface::LINK_EXTERNAL];
- $this->fieldUIAddNewField($type_path, $field_name, $label, 'link', [], $field_edit);
+ /**
+ * Checks that given field contains the given raw text.
+ *
+ * @param string $field_name
+ * The name of the field to check.
+ * @param string $text
+ * The text to check.
+ */
+ protected function assertFieldContainsRawText($field_name, $text) {
+ $this->assertTrue((bool) preg_match('/' . preg_quote($text, '/') . '/ui', $this->getFieldHtml($field_name)));
+ }
- // Test the help text displays when link allows only external links.
- $this->drupalLogin($this->drupalCreateUser(['create ' . $type->id() . ' content']));
- $this->drupalGet($add_path);
- $this->assertRaw('This must be an external URL such as http://example.com.');
+ /**
+ * Checks that given field does not contain the given raw text.
+ *
+ * @param string $field_name
+ * The name of the field to check.
+ * @param string $text
+ * The text to check.
+ */
+ protected function assertNoFieldContainsRawText($field_name, $text) {
+ $this->assertFalse((bool) preg_match('/' . preg_quote($text, '/') . '/ui', $this->getFieldHtml($field_name)));
+ }
+
+ /**
+ * Returns the raw HTML for the given field.
+ *
+ * @param $field_name
+ * The name of the field for which to return the HTML.
+ *
+ * @return string
+ * The raw HTML.
+ */
+ protected function getFieldHtml($field_name) {
+ $css_id = Html::cleanCssIdentifier('edit-' . $field_name . '-wrapper');
+ return $this->xpath('//*[@id=:id]', [':id' => $css_id])[0]->getHtml();
}
}
diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
index 464b86c3972..8796bc74887 100644
--- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
+++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
@@ -78,6 +78,8 @@ class MenuLinkContentFormTest extends BrowserTestBase {
$element = $this->xpath('//select[@id = :id]/option[@selected]', [':id' => 'edit-menu-parent']);
$this->assertTrue($element, 'A default menu parent was found.');
$this->assertEqual('admin:', $element[0]->getValue(), ' menu is the parent.');
+ // Test that the field description is present.
+ $this->assertRaw('The location this menu link points to.');
$this->drupalPostForm(
NULL,
diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php b/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php
index e39f652e55e..77e88959b97 100644
--- a/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php
+++ b/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php
@@ -65,6 +65,11 @@ class ShortcutLinksTest extends ShortcutTestBase {
'/admin/config/system/site-information',
];
+ // Test the add shortcut form UI. Test that the base field description is
+ // there.
+ $this->drupalGet('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link');
+ $this->assertRaw('The location this shortcut points to.');
+
// Check that each new shortcut links where it should.
foreach ($test_cases as $test_path) {
$title = $this->randomMachineName();