function LinkWidget::formElement

Same name in other branches
  1. 9 core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::formElement()
  2. 8.9.x core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::formElement()
  3. 10 core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php \Drupal\link\Plugin\Field\FieldWidget\LinkWidget::formElement()

Overrides WidgetInterface::formElement

File

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

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];
    $display_uri = NULL;
    if (!$item->isEmpty()) {
        try {
            // 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.
            if (\Drupal::currentUser()->hasPermission('link to any page') || $item->getUrl()
                ->access()) {
                $display_uri = static::getUriAsDisplayableString($item->uri);
            }
        } catch (\InvalidArgumentException) {
            // If $item->uri is invalid, show value as is, so the user can see what
            // to edit.
            // @todo Add logging here in https://www.drupal.org/project/drupal/issues/3348020
            $display_uri = $item->uri;
        }
    }
    $element['uri'] = [
        '#type' => 'url',
        '#title' => $this->t('URL'),
        '#placeholder' => $this->getSetting('placeholder_url'),
        '#default_value' => $display_uri,
        '#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' => 'https://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' => 'https://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;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.