Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 169 |
| LinkWidget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
2352 | |
0.00% |
0 / 169 |
| defaultSettings | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 5 |
|||
| getUriAsDisplayableString | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 19 |
|||
| getUserEnteredStringAsUri | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 13 |
|||
| validateUriElement | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 7 |
|||
| validateTitleElement | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
| formElement | |
0.00% |
0 / 1 |
272 | |
0.00% |
0 / 54 |
|||
| supportsInternalLinks | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| supportsExternalLinks | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| settingsForm | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 20 |
|||
| settingsSummary | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 16 |
|||
| massageFormValues | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
| flagErrors | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 18 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\link\Plugin\Field\FieldWidget\LinkWidget. | |
| */ | |
| namespace Drupal\link\Plugin\Field\FieldWidget; | |
| use Drupal\Core\Entity\Element\EntityAutocomplete; | |
| use Drupal\Core\Field\FieldItemListInterface; | |
| use Drupal\Core\Field\WidgetBase; | |
| use Drupal\Core\Form\FormStateInterface; | |
| use Drupal\link\LinkItemInterface; | |
| use Symfony\Component\Validator\ConstraintViolation; | |
| use Symfony\Component\Validator\ConstraintViolationListInterface; | |
| /** | |
| * Plugin implementation of the 'link' widget. | |
| * | |
| * @FieldWidget( | |
| * id = "link_default", | |
| * label = @Translation("Link"), | |
| * field_types = { | |
| * "link" | |
| * } | |
| * ) | |
| */ | |
| class LinkWidget extends WidgetBase { | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public static function defaultSettings() { | |
| return array( | |
| 'placeholder_url' => '', | |
| 'placeholder_title' => '', | |
| ) + parent::defaultSettings(); | |
| } | |
| /** | |
| * Gets the URI without the 'internal:' or 'entity:' scheme. | |
| * | |
| * The following two forms of URIs are transformed: | |
| * - 'entity:' URIs: to entity autocomplete ("label (entity id)") strings; | |
| * - 'internal:' URIs: the scheme is stripped. | |
| * | |
| * This method is the inverse of ::getUserEnteredStringAsUri(). | |
| * | |
| * @param string $uri | |
| * The URI to get the displayable string for. | |
| * | |
| * @return string | |
| * | |
| * @see static::getUserEnteredStringAsUri() | |
| */ | |
| protected static function getUriAsDisplayableString($uri) { | |
| $scheme = parse_url($uri, PHP_URL_SCHEME); | |
| // By default, the displayable string is the URI. | |
| $displayable_string = $uri; | |
| // A different displayable string may be chosen in case of the 'internal:' | |
| // or 'entity:' built-in schemes. | |
| if ($scheme === 'internal') { | |
| $uri_reference = explode(':', $uri, 2)[1]; | |
| // @todo '<front>' is valid input for BC reasons, may be removed by | |
| // https://www.drupal.org/node/2421941 | |
| $path = parse_url($uri, PHP_URL_PATH); | |
| if ($path === '/') { | |
| $uri_reference = '<front>' . substr($uri_reference, 1); | |
| } | |
| $displayable_string = $uri_reference; | |
| } | |
| elseif ($scheme === 'entity') { | |
| list($entity_type, $entity_id) = explode('/', substr($uri, 7), 2); | |
| // Show the 'entity:' URI as the entity autocomplete would. | |
| $entity_manager = \Drupal::entityManager(); | |
| if ($entity_manager->getDefinition($entity_type, FALSE) && $entity = \Drupal::entityManager()->getStorage($entity_type)->load($entity_id)) { | |
| $displayable_string = EntityAutocomplete::getEntityLabels(array($entity)); | |
| } | |
| } | |
| return $displayable_string; | |
| } | |
| /** | |
| * Gets the user-entered string as a URI. | |
| * | |
| * The following two forms of input are mapped to URIs: | |
| * - entity autocomplete ("label (entity id)") strings: to 'entity:' URIs; | |
| * - strings without a detectable scheme: to 'internal:' URIs. | |
| * | |
| * This method is the inverse of ::getUriAsDisplayableString(). | |
| * | |
| * @param string $string | |
| * The user-entered string. | |
| * | |
| * @return string | |
| * The URI, if a non-empty $uri was passed. | |
| * | |
| * @see static::getUriAsDisplayableString() | |
| */ | |
| protected static function getUserEnteredStringAsUri($string) { | |
| // By default, assume the entered string is an URI. | |
| $uri = $string; | |
| // Detect entity autocomplete string, map to 'entity:' URI. | |
| $entity_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($string); | |
| if ($entity_id !== NULL) { | |
| // @todo Support entity types other than 'node'. Will be fixed in | |
| // https://www.drupal.org/node/2423093. | |
| $uri = 'entity:node/' . $entity_id; | |
| } | |
| // Detect a schemeless string, map to 'internal:' URI. | |
| elseif (!empty($string) && parse_url($string, PHP_URL_SCHEME) === NULL) { | |
| // @todo '<front>' is valid input for BC reasons, may be removed by | |
| // https://www.drupal.org/node/2421941 | |
| // - '<front>' -> '/' | |
| // - '<front>#foo' -> '/#foo' | |
| if (strpos($string, '<front>') === 0) { | |
| $string = '/' . substr($string, strlen('<front>')); | |
| } | |
| $uri = 'internal:' . $string; | |
| } | |
| return $uri; | |
| } | |
| /** | |
| * Form element validation handler for the 'uri' element. | |
| * | |
| * Disallows saving inaccessible or untrusted URLs. | |
| */ | |
| public static function validateUriElement($element, FormStateInterface $form_state, $form) { | |
| $uri = static::getUserEnteredStringAsUri($element['#value']); | |
| $form_state->setValueForElement($element, $uri); | |
| // If getUserEnteredStringAsUri() mapped the entered value to a 'internal:' | |
| // URI , ensure the raw value begins with '/', '?' or '#'. | |
| // @todo '<front>' is valid input for BC reasons, may be removed by | |
| // https://www.drupal.org/node/2421941 | |
| if (parse_url($uri, PHP_URL_SCHEME) === 'internal' && !in_array($element['#value'][0], ['/', '?', '#'], TRUE) && substr($element['#value'], 0, 7) !== '<front>') { | |
| $form_state->setError($element, t('Manually entered paths should start with /, ? or #.')); | |
| return; | |
| } | |
| } | |
| /** | |
| * Form element validation handler for the 'title' element. | |
| * | |
| * Conditionally requires the link title if a URL value was filled in. | |
| */ | |
| public static function validateTitleElement(&$element, FormStateInterface $form_state, $form) { | |
| if ($element['uri']['#value'] !== '' && $element['title']['#value'] === '') { | |
| $element['title']['#required'] = TRUE; | |
| // We expect the field name placeholder value to be wrapped in t() here, | |
| // so it won't be escaped again as it's already marked safe. | |
| $form_state->setError($element['title'], t('@name field is required.', array('@name' => $element['title']['#title']))); | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { | |
| /** @var \Drupal\link\LinkItemInterface $item */ | |
| $item = $items[$delta]; | |
| $element['uri'] = array( | |
| '#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' => array(array(get_called_class(), 'validateUriElement')), | |
| '#maxlength' => 2048, | |
| '#required' => $element['#required'], | |
| ); | |
| // 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. | |
| if (!$this->supportsExternalLinks()) { | |
| $element['uri']['#field_prefix'] = rtrim(\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['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.', array('%front' => '<front>', '%add-node' => '/node/add', '%url' => 'http://example.com')); | |
| } | |
| // If the field is configured to allow only external links, show a useful | |
| // description. | |
| elseif ($this->supportsExternalLinks() && !$this->supportsInternalLinks()) { | |
| $element['uri']['#description'] = $this->t('This must be an external URL such as %url.', array('%url' => 'http://example.com')); | |
| } | |
| $element['title'] = array( | |
| '#type' => 'textfield', | |
| '#title' => $this->t('Link text'), | |
| '#placeholder' => $this->getSetting('placeholder_title'), | |
| '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL, | |
| '#maxlength' => 255, | |
| '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED, | |
| ); | |
| // 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'][] = array(get_called_class(), 'validateTitleElement'); | |
| } | |
| // Exposing the attributes array in the widget is left for alternate and more | |
| // advanced field widgets. | |
| $element['attributes'] = array( | |
| '#type' => 'value', | |
| '#tree' => TRUE, | |
| '#value' => !empty($items[$delta]->options['attributes']) ? $items[$delta]->options['attributes'] : array(), | |
| '#attributes' => array('class' => array('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']; | |
| } | |
| // Otherwise wrap everything in a details element. | |
| else { | |
| $element += array( | |
| '#type' => 'fieldset', | |
| ); | |
| } | |
| } | |
| return $element; | |
| } | |
| /** | |
| * 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); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function settingsForm(array $form, FormStateInterface $form_state) { | |
| $elements = parent::settingsForm($form, $form_state); | |
| $elements['placeholder_url'] = array( | |
| '#type' => 'textfield', | |
| '#title' => $this->t('Placeholder for URL'), | |
| '#default_value' => $this->getSetting('placeholder_url'), | |
| '#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.'), | |
| ); | |
| $elements['placeholder_title'] = array( | |
| '#type' => 'textfield', | |
| '#title' => $this->t('Placeholder for link text'), | |
| '#default_value' => $this->getSetting('placeholder_title'), | |
| '#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.'), | |
| '#states' => array( | |
| 'invisible' => array( | |
| ':input[name="instance[settings][title]"]' => array('value' => DRUPAL_DISABLED), | |
| ), | |
| ), | |
| ); | |
| return $elements; | |
| } | |
| /** | |
| * {@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)) { | |
| $summary[] = $this->t('No placeholders'); | |
| } | |
| else { | |
| if (!empty($placeholder_title)) { | |
| $summary[] = $this->t('Title placeholder: @placeholder_title', array('@placeholder_title' => $placeholder_title)); | |
| } | |
| if (!empty($placeholder_url)) { | |
| $summary[] = $this->t('URL placeholder: @placeholder_url', array('@placeholder_url' => $placeholder_url)); | |
| } | |
| } | |
| return $summary; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { | |
| foreach ($values as &$value) { | |
| $value['uri'] = static::getUserEnteredStringAsUri($value['uri']); | |
| $value += ['options' => []]; | |
| } | |
| return $values; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| * | |
| * Override the '%uri' message parameter, to ensure that 'internal:' URIs | |
| * show a validation error message that doesn't mention that scheme. | |
| */ | |
| public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) { | |
| /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */ | |
| foreach ($violations as $offset => $violation) { | |
| $parameters = $violation->getParameters(); | |
| if (isset($parameters['@uri'])) { | |
| $parameters['@uri'] = static::getUriAsDisplayableString($parameters['@uri']); | |
| $violations->set($offset, new ConstraintViolation( | |
| $this->t($violation->getMessageTemplate(), $parameters), | |
| $violation->getMessageTemplate(), | |
| $parameters, | |
| $violation->getRoot(), | |
| $violation->getPropertyPath(), | |
| $violation->getInvalidValue(), | |
| $violation->getPlural(), | |
| $violation->getCode() | |
| )); | |
| } | |
| } | |
| parent::flagErrors($items, $violations, $form, $form_state); | |
| } | |
| } |