You are here

public function LinkWidget::formElement in Drupal 10

Same name and namespace in other branches
  1. 8 core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::formElement()
  2. 9 core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::formElement()

Returns the form for a single field widget.

Field widget form elements should be based on the passed-in $element, which contains the base form element properties derived from the field configuration.

The BaseWidget methods will set the weight, field name and delta values for each form element. If there are multiple values for this field, the formElement() method will be called as many times as needed.

Other modules may alter the form element provided by this function using hook_field_widget_single_element_form_alter() or hook_field_widget_single_element_WIDGET_TYPE_form_alter().

The FAPI element callbacks (such as #process, #element_validate, #value_callback, etc.) used by the widget do not have access to the original $field_definition passed to the widget's constructor. Therefore, if any information is needed from that definition by those callbacks, the widget implementing this method, or a hook_field_widget[_WIDGET_TYPE]_form_alter() implementation, must extract the needed properties from the field definition and set them as ad-hoc $element['#custom'] properties, for later use by its element callbacks.

Parameters

\Drupal\Core\Field\FieldItemListInterface $items: Array of default values for this field.

int $delta: The order of this item in the array of sub-elements (0, 1, 2, etc.).

array $element: A form element array containing basic properties for the widget:

  • #field_parents: The 'parents' space for the field in the form. Most widgets can simply overlook this property. This identifies the location where the field values are placed within $form_state->getValues(), and is used to access processing information for the field through the getWidgetState() and setWidgetState() methods.
  • #title: The sanitized element label for the field, ready for output.
  • #description: The sanitized element description for the field, ready for output.
  • #required: A Boolean indicating whether the element value is required; for required multiple value fields, only the first widget's values are required.
  • #delta: The order of this item in the array of sub-elements; see $delta above.

array $form: The form structure where widgets are being attached to. This might be a full form structure, or a sub-element of a larger form.

\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form.

Return value

array The form elements for a single widget for this field.

Overrides WidgetInterface::formElement

See also

hook_field_widget_single_element_form_alter()

hook_field_widget_single_element_WIDGET_TYPE_form_alter()

File

core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php, line 182

Class

LinkWidget
Plugin implementation of the 'link' widget.

Namespace

Drupal\link\Plugin\Field\FieldWidget

Code

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {

  /** @var \Drupal\link\LinkItemInterface $item */
  $item = $items[$delta];
  $element['uri'] = [
    '#type' => 'url',
    '#title' => $this
      ->t('URL'),
    '#placeholder' => $this
      ->getSetting('placeholder_url'),
    // The current field value could have been entered by a different user.
    // However, if it is inaccessible to the current user, do not display it
    // to them.
    '#default_value' => !$item
      ->isEmpty() && (\Drupal::currentUser()
      ->hasPermission('link to any page') || $item
      ->getUrl()
      ->access()) ? static::getUriAsDisplayableString($item->uri) : NULL,
    '#element_validate' => [
      [
        static::class,
        'validateUriElement',
      ],
    ],
    '#maxlength' => 2048,
    '#required' => $element['#required'],
    '#link_type' => $this
      ->getFieldSetting('link_type'),
  ];

  // If the field is configured to support internal links, it cannot use the
  // 'url' form element and we have to do the validation ourselves.
  if ($this
    ->supportsInternalLinks()) {
    $element['uri']['#type'] = 'entity_autocomplete';

    // @todo The user should be able to select an entity type. Will be fixed
    //   in https://www.drupal.org/node/2423093.
    $element['uri']['#target_type'] = 'node';

    // Disable autocompletion when the first character is '/', '#' or '?'.
    $element['uri']['#attributes']['data-autocomplete-first-character-blacklist'] = '/#?';

    // The link widget is doing its own processing in
    // static::getUriAsDisplayableString().
    $element['uri']['#process_default_value'] = FALSE;
  }

  // If the field is configured to allow only internal links, add a useful
  // element prefix and description.
  if (!$this
    ->supportsExternalLinks()) {
    $element['uri']['#field_prefix'] = rtrim(Url::fromRoute('<front>', [], [
      'absolute' => TRUE,
    ])
      ->toString(), '/');
    $element['uri']['#description'] = $this
      ->t('This must be an internal path such as %add-node. You can also start typing the title of a piece of content to select it. Enter %front to link to the front page. Enter %nolink to display link text only. Enter %button to display keyboard-accessible link text only.', [
      '%add-node' => '/node/add',
      '%front' => '<front>',
      '%nolink' => '<nolink>',
      '%button' => '<button>',
    ]);
  }
  elseif ($this
    ->supportsExternalLinks() && $this
    ->supportsInternalLinks()) {
    $element['uri']['#description'] = $this
      ->t('Start typing the title of a piece of content to select it. You can also enter an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page. Enter %nolink to display link text only. Enter %button to display keyboard-accessible link text only.', [
      '%front' => '<front>',
      '%add-node' => '/node/add',
      '%url' => 'http://example.com',
      '%nolink' => '<nolink>',
      '%button' => '<button>',
    ]);
  }
  elseif ($this
    ->supportsExternalLinks() && !$this
    ->supportsInternalLinks()) {
    $element['uri']['#description'] = $this
      ->t('This must be an external URL such as %url.', [
      '%url' => 'http://example.com',
    ]);
  }

  // Make uri required on the front-end when title filled-in.
  if (!$this
    ->isDefaultValueWidget($form_state) && $this
    ->getFieldSetting('title') !== DRUPAL_DISABLED && !$element['uri']['#required']) {
    $parents = $element['#field_parents'];
    $parents[] = $this->fieldDefinition
      ->getName();
    $selector = $root = array_shift($parents);
    if ($parents) {
      $selector = $root . '[' . implode('][', $parents) . ']';
    }
    $element['uri']['#states']['required'] = [
      ':input[name="' . $selector . '[' . $delta . '][title]"]' => [
        'filled' => TRUE,
      ],
    ];
  }
  $element['title'] = [
    '#type' => 'textfield',
    '#title' => $this
      ->t('Link text'),
    '#placeholder' => $this
      ->getSetting('placeholder_title'),
    '#default_value' => $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.
  //
  // Validate that title field is filled out (regardless of uri) when it is a
  // required field.
  if (!$this
    ->isDefaultValueWidget($form_state) && $this
    ->getFieldSetting('title') === DRUPAL_REQUIRED) {
    $element['#element_validate'][] = [
      static::class,
      'validateTitleElement',
    ];
    $element['#element_validate'][] = [
      static::class,
      'validateTitleNoLink',
    ];
    if (!$element['title']['#required']) {

      // Make title required on the front-end when URI filled-in.
      $parents = $element['#field_parents'];
      $parents[] = $this->fieldDefinition
        ->getName();
      $selector = $root = array_shift($parents);
      if ($parents) {
        $selector = $root . '[' . implode('][', $parents) . ']';
      }
      $element['title']['#states']['required'] = [
        ':input[name="' . $selector . '[' . $delta . '][uri]"]' => [
          'filled' => TRUE,
        ],
      ];
    }
  }

  // Ensure that a URI is always entered when an optional title field is
  // submitted.
  if (!$this
    ->isDefaultValueWidget($form_state) && $this
    ->getFieldSetting('title') == DRUPAL_OPTIONAL) {
    $element['#element_validate'][] = [
      static::class,
      'validateTitleNoLink',
    ];
  }

  // Exposing the attributes array in the widget is left for alternate and more
  // advanced field widgets.
  $element['attributes'] = [
    '#type' => 'value',
    '#tree' => TRUE,
    '#value' => !empty($items[$delta]->options['attributes']) ? $items[$delta]->options['attributes'] : [],
    '#attributes' => [
      'class' => [
        'link-field-widget-attributes',
      ],
    ],
  ];

  // If cardinality is 1, ensure a proper label is output for the field.
  if ($this->fieldDefinition
    ->getFieldStorageDefinition()
    ->getCardinality() == 1) {

    // If the link title is disabled, use the field definition label as the
    // 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'],
            ],
          ];
        }
      }
    }
    else {
      $element += [
        '#type' => 'fieldset',
      ];
    }
  }
  return $element;
}