LinkWidget.php 8.36 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\link\Plugin\Field\FieldWidget\LinkWidget.
6 7
 */

8
namespace Drupal\link\Plugin\Field\FieldWidget;
9

10
use Drupal\Component\Utility\UrlHelper;
11
use Drupal\Core\Field\FieldItemListInterface;
12
use Drupal\Core\Field\WidgetBase;
13 14 15 16 17
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18 19 20 21

/**
 * Plugin implementation of the 'link' widget.
 *
22
 * @FieldWidget(
23 24 25 26 27 28 29 30 31
 *   id = "link_default",
 *   label = @Translation("Link"),
 *   field_types = {
 *     "link"
 *   }
 * )
 */
class LinkWidget extends WidgetBase {

32 33 34 35 36 37 38 39 40 41
  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array(
      'placeholder_url' => '',
      'placeholder_title' => '',
    ) + parent::defaultSettings();
  }

42 43 44
  /**
   * {@inheritdoc}
   */
45
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
46 47 48 49 50 51 52

    $default_url_value = NULL;
    if (isset($items[$delta]->url)) {
      $url = Url::createFromPath($items[$delta]->url);
      $url->setOptions($items[$delta]->options);
      $default_url_value = ltrim($url->toString(), '/');
    }
53 54
    $element['url'] = array(
      '#type' => 'url',
55
      '#title' => $this->t('URL'),
56
      '#placeholder' => $this->getSetting('placeholder_url'),
57
      '#default_value' => $default_url_value,
58 59 60
      '#maxlength' => 2048,
      '#required' => $element['#required'],
    );
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

    // 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['url']['#type'] = 'textfield';
    }

    // If the field is configured to allow only internal links, add a useful
    // element prefix.
    if (!$this->supportsExternalLinks()) {
      $element['url']['#field_prefix'] = \Drupal::url('<front>', array(), array('absolute' => TRUE));
    }
    // If the field is configured to allow both internal and external links,
    // show a useful description.
    elseif ($this->supportsExternalLinks() && $this->supportsInternalLinks()) {
      $element['url']['#description'] = $this->t('This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org'));
    }

79 80
    $element['title'] = array(
      '#type' => 'textfield',
81
      '#title' => $this->t('Link text'),
82
      '#placeholder' => $this->getSetting('placeholder_title'),
83
      '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL,
84
      '#maxlength' => 255,
85
      '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED,
86 87 88 89 90
    );
    // 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.
    $is_field_edit_form = ($element['#entity'] === NULL);
91
    if (!$is_field_edit_form && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
92
      $element['#element_validate'][] = array($this, 'validateTitle');
93 94 95 96 97 98 99
    }

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

    // If cardinality is 1, ensure a label is output for the field by wrapping it
    // in a details element.
106
    if ($this->fieldDefinition->getCardinality() == 1) {
107 108 109 110 111 112 113 114
      $element += array(
        '#type' => 'fieldset',
      );
    }

    return $element;
  }

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  /**
   * Indicates enabled support for link to routes.
   *
   * @return bool
   *   Returns TRUE if the LinkItem field is configured to support links to
   *   routes, otherwise FALSE.
   */
  protected function supportsInternalLinks() {
    $link_type = $this->getFieldSetting('link_type');
    return (bool) ($link_type & LinkItemInterface::LINK_INTERNAL);
  }

  /**
   * Indicates enabled support for link to external URLs.
   *
   * @return bool
   *   Returns TRUE if the LinkItem field is configured to support links to
   *   external URLs, otherwise FALSE.
   */
  protected function supportsExternalLinks() {
    $link_type = $this->getFieldSetting('link_type');
    return (bool) ($link_type & LinkItemInterface::LINK_EXTERNAL);
  }

139 140 141 142 143 144 145 146
  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, array &$form_state) {
    $elements = parent::settingsForm($form, $form_state);

    $elements['placeholder_url'] = array(
      '#type' => 'textfield',
147
      '#title' => $this->t('Placeholder for URL'),
148
      '#default_value' => $this->getSetting('placeholder_url'),
149
      '#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
150 151 152
    );
    $elements['placeholder_title'] = array(
      '#type' => 'textfield',
153
      '#title' => $this->t('Placeholder for link text'),
154
      '#default_value' => $this->getSetting('placeholder_title'),
155
      '#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
156 157 158 159 160 161 162 163 164 165
      '#states' => array(
        'invisible' => array(
          ':input[name="instance[settings][title]"]' => array('value' => DRUPAL_DISABLED),
        ),
      ),
    );

    return $elements;
  }

166 167 168 169 170 171 172 173 174
  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = array();

    $placeholder_title = $this->getSetting('placeholder_title');
    $placeholder_url = $this->getSetting('placeholder_url');
    if (empty($placeholder_title) && empty($placeholder_url)) {
175
      $summary[] = $this->t('No placeholders');
176 177 178
    }
    else {
      if (!empty($placeholder_title)) {
179
        $summary[] = $this->t('Title placeholder: @placeholder_title', array('@placeholder_title' => $placeholder_title));
180 181
      }
      if (!empty($placeholder_url)) {
182
        $summary[] = $this->t('URL placeholder: @placeholder_url', array('@placeholder_url' => $placeholder_url));
183 184 185 186 187 188
      }
    }

    return $summary;
  }

189
  /**
190
   * Form element validation handler; Validates the title property.
191 192 193
   *
   * Conditionally requires the link title if a URL value was filled in.
   */
194
  public function validateTitle(&$element, &$form_state, $form) {
195 196
    if ($element['url']['#value'] !== '' && $element['title']['#value'] === '') {
      $element['title']['#required'] = TRUE;
197
      \Drupal::formBuilder()->setError($element['title'], $form_state, $this->t('!name field is required.', array('!name' => $element['title']['#title'])));
198 199 200
    }
  }

201 202 203 204 205 206 207 208 209
  /**
   * {@inheritdoc}
   */
  public function massageFormValues(array $values, array $form, array &$form_state) {
    foreach ($values as &$value) {
      if (!empty($value['url'])) {
        try {
          $parsed_url = UrlHelper::parse($value['url']);

210 211 212
          // If internal links are supported, look up whether the given value is
          // a path alias and store the system path instead.
          if ($this->supportsInternalLinks() && !UrlHelper::isExternal($value['url'])) {
213
            $parsed_url['path'] = \Drupal::service('path.alias_manager.cached')->getPathByAlias($parsed_url['path']);
214 215
          }

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
          $url = Url::createFromPath($parsed_url['path']);
          $url->setOption('query', $parsed_url['query']);
          $url->setOption('fragment', $parsed_url['fragment']);
          $url->setOption('attributes', $value['attributes']);

          $value += $url->toArray();
          // Reset the URL value to contain only the path.
          $value['url'] = $parsed_url['path'];
        }
        catch (NotFoundHttpException $e) {
          // Nothing to do here, LinkTypeConstraintValidator emits errors.
        }
        catch (MatchingRouteNotFoundException $e) {
          // Nothing to do here, LinkTypeConstraintValidator emits errors.
        }
        catch (ParamNotConvertedException $e) {
          // Nothing to do here, LinkTypeConstraintValidator emits errors.
        }
      }
    }
    return $values;
  }

}