Last active
August 17, 2018 16:17
-
-
Save ahebrank/bb2ccba70b2f312e2841b710cc11d777 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/config/schema/linkit.schema.yml b/config/schema/linkit.schema.yml | |
| index 882a2f1..af556bb 100644 | |
| --- a/config/schema/linkit.schema.yml | |
| +++ b/config/schema/linkit.schema.yml | |
| @@ -110,3 +110,21 @@ ckeditor.plugin.drupallink: | |
| linkit_profile: | |
| type: string | |
| label: 'Linkit profile' | |
| + | |
| +# Schema for the Linkit widget. | |
| +field.widget.settings.linkit: | |
| + type: field.widget.settings.link_default | |
| + label: 'Linkit widget settings' | |
| + mapping: | |
| + linkit_profile: | |
| + type: string | |
| + label: 'Linkit profile' | |
| + | |
| +# Schema for the Linkit formatter. | |
| +field.formatter.settings.linkit: | |
| + type: field.formatter.settings.link | |
| + label: 'Linkit format settings' | |
| + mapping: | |
| + linkit_profile: | |
| + type: string | |
| + label: 'Linkit profile' | |
| diff --git a/js/autocomplete.js b/js/autocomplete.js | |
| index 558278d..47fd1ae 100644 | |
| --- a/js/autocomplete.js | |
| +++ b/js/autocomplete.js | |
| @@ -63,24 +63,39 @@ | |
| * False to prevent further handlers. | |
| */ | |
| function selectHandler(event, ui) { | |
| - var $form = $(event.target).closest('form'); | |
| + var $context = $(event.target).closest('form,fieldset,tr'); | |
| + | |
| if (!ui.item.path) { | |
| throw 'Missing path param.' + JSON.stringify(ui.item); | |
| } | |
| - $('input[name="href_dirty_check"]', $form).val(ui.item.path); | |
| + $('input[name="href_dirty_check"]', $context).val(ui.item.path); | |
| if (ui.item.entity_type_id || ui.item.entity_uuid || ui.item.substitution_id) { | |
| if (!ui.item.entity_type_id || !ui.item.entity_uuid || !ui.item.substitution_id) { | |
| throw 'Missing path param.' + JSON.stringify(ui.item); | |
| } | |
| + } | |
| + $('input[name="attributes[href]"], input[name$="[attributes][href]"]', $context).val(ui.item.path); | |
| + $('input[name="attributes[data-entity-type]"], input[name$="[attributes][data-entity-type]"]', $context).val(ui.item.entity_type_id); | |
| + $('input[name="attributes[data-entity-uuid]"], input[name$="[attributes][data-entity-uuid]"]', $context).val(ui.item.entity_uuid); | |
| + $('input[name="attributes[data-entity-substitution]"], input[name$="[attributes][data-entity-substitution]"]', $context).val(ui.item.substitution_id); | |
| + | |
| + if (ui.item.label) { | |
| + // Automatically set the link title. | |
| + var $linkTitle = $(event.target).closest('.form-item').siblings('.form-type-textfield').find('.linkit-widget-title'); | |
| + if ($linkTitle.length > 0) { | |
| + if (!$linkTitle.val() || $linkTitle.hasClass('link-widget-title--auto')) { | |
| + // Set value to the label. | |
| + $linkTitle.val(ui.item.label); | |
| - $('input[name="attributes[data-entity-type]"]', $form).val(ui.item.entity_type_id); | |
| - $('input[name="attributes[data-entity-uuid]"]', $form).val(ui.item.entity_uuid); | |
| - $('input[name="attributes[data-entity-substitution]"]', $form).val(ui.item.substitution_id); | |
| + // Flag title as being automatically set. | |
| + $linkTitle.addClass('link-widget-title--auto'); | |
| + } | |
| + } | |
| } | |
| - event.target.value = ui.item.path; | |
| + event.target.value = ui.item.value; | |
| return false; | |
| } | |
| @@ -172,7 +187,26 @@ | |
| $autocomplete.autocomplete('widget').addClass('linkit-ui-autocomplete'); | |
| $autocomplete.click(function () { | |
| - $autocomplete.autocomplete('search', $autocomplete.val()); | |
| + var $this = $(this); | |
| + $this.autocomplete('search', $this.val()); | |
| + }); | |
| + | |
| + // Process each item. | |
| + $autocomplete.each(function () { | |
| + var $uri = $(this); | |
| + $uri.closest('.form-item').siblings('.form-type-textfield').find('.linkit-widget-title') | |
| + .each(function() { | |
| + // Set automatic title flag if title is the same as uri text. | |
| + var $title = $(this); | |
| + var uriValue = $uri.val(); | |
| + if (uriValue && uriValue === $title.val()) { | |
| + $title.addClass('link-widget-title--auto'); | |
| + } | |
| + }) | |
| + .change(function () { | |
| + // Remove automatic title flag. | |
| + $(this).removeClass('link-widget-title--auto'); | |
| + }); | |
| }); | |
| $autocomplete.on('compositionstart.autocomplete', function () { | |
| diff --git a/linkit.module b/linkit.module | |
| index affeaeb..c65b99a 100644 | |
| --- a/linkit.module | |
| +++ b/linkit.module | |
| @@ -125,15 +125,30 @@ function linkit_form_editor_link_dialog_submit(array &$form, FormStateInterface | |
| $href = $form_state->getValue(['attributes', 'href']); | |
| $href_dirty_check = $form_state->getValue(['href_dirty_check']); | |
| + $reset_attributes = ($href !== $href_dirty_check); | |
| + // Do not reset the attributes if the text present on the URL field | |
| + // corresponds to the entity's label. | |
| + $entity_type_id = $form_state->getValue(['attributes', 'data-entity-type']); | |
| + $entity_uuid = $form_state->getValue(['attributes', 'data-entity-uuid']); | |
| + if ($entity_type_id && $entity_uuid) { | |
| + /** @var \Drupal\Core\Entity\EntityInterface $entity */ | |
| + $entity = \Drupal::service('entity.repository')->loadEntityByUuid($entity_type_id, $entity_uuid); | |
| + if ($entity) { | |
| + $translation = \Drupal::service('entity.repository')->getTranslationFromContext($entity); | |
| + if (strpos(html_entity_decode($href), $translation->label()) !== FALSE) { | |
| + $reset_attributes = FALSE; | |
| + $form_state->setValue(['attributes', 'href'], $href_dirty_check); | |
| + } | |
| + } | |
| + } | |
| - if ($href !== $href_dirty_check) { | |
| + if ($reset_attributes) { | |
| $form_state->unsetValue(['attributes', 'data-entity-type']); | |
| $form_state->unsetValue(['attributes', 'data-entity-uuid']); | |
| $form_state->unsetValue(['attributes', 'data-entity-substitution']); | |
| } | |
| $fields = [ | |
| - 'href', | |
| 'data-entity-type', | |
| 'data-entity-uuid', | |
| 'data-entity-substitution', | |
| @@ -150,4 +165,5 @@ function linkit_form_editor_link_dialog_submit(array &$form, FormStateInterface | |
| } | |
| } | |
| } | |
| + | |
| } | |
| diff --git a/src/Entity/Profile.php b/src/Entity/Profile.php | |
| index 651242f..070e984 100644 | |
| --- a/src/Entity/Profile.php | |
| +++ b/src/Entity/Profile.php | |
| @@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; | |
| use Drupal\Core\Entity\EntityWithPluginCollectionInterface; | |
| use Drupal\linkit\MatcherCollection; | |
| use Drupal\linkit\MatcherInterface; | |
| +use Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher; | |
| use Drupal\linkit\ProfileInterface; | |
| /** | |
| @@ -108,6 +109,19 @@ class Profile extends ConfigEntityBase implements ProfileInterface, EntityWithPl | |
| return $this->getMatchers()->get($instance_id); | |
| } | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function getMatcherByEntityType($entity_type_id) { | |
| + foreach ($this->getMatchers() as $matcher) { | |
| + if ($matcher instanceof EntityMatcher && $matcher->getPluginDefinition()['target_entity'] === $entity_type_id) { | |
| + return $matcher; | |
| + } | |
| + } | |
| + | |
| + return NULL; | |
| + } | |
| + | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| diff --git a/src/Plugin/Field/FieldFormatter/LinkitFormatter.php b/src/Plugin/Field/FieldFormatter/LinkitFormatter.php | |
| new file mode 100644 | |
| index 0000000..0b0bd26 | |
| --- /dev/null | |
| +++ b/src/Plugin/Field/FieldFormatter/LinkitFormatter.php | |
| @@ -0,0 +1,135 @@ | |
| +<?php | |
| + | |
| +namespace Drupal\linkit\Plugin\Field\FieldFormatter; | |
| + | |
| +use Drupal\Core\Field\FieldDefinitionInterface; | |
| +use Drupal\Core\Field\FieldItemListInterface; | |
| +use Drupal\Core\Path\PathValidatorInterface; | |
| +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; | |
| +use Drupal\link\LinkItemInterface; | |
| +use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter; | |
| +use Drupal\linkit\Entity\Profile; | |
| +use Drupal\linkit\SubstitutionManagerInterface; | |
| +use Drupal\linkit\Utility\LinkitHelper; | |
| +use Symfony\Component\DependencyInjection\ContainerInterface; | |
| + | |
| +/** | |
| + * Plugin implementation of the 'linkit' formatter. | |
| + * | |
| + * @FieldFormatter( | |
| + * id = "linkit", | |
| + * label = @Translation("Linkit"), | |
| + * field_types = { | |
| + * "link" | |
| + * } | |
| + * ) | |
| + */ | |
| +class LinkitFormatter extends LinkFormatter implements ContainerFactoryPluginInterface { | |
| + | |
| + /** | |
| + * The substitution manager. | |
| + * | |
| + * @var \Drupal\linkit\SubstitutionManagerInterface | |
| + */ | |
| + protected $substitutionManager; | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
| + return new static( | |
| + $plugin_id, | |
| + $plugin_definition, | |
| + $configuration['field_definition'], | |
| + $configuration['settings'], | |
| + $configuration['label'], | |
| + $configuration['view_mode'], | |
| + $configuration['third_party_settings'], | |
| + $container->get('path.validator'), | |
| + $container->get('plugin.manager.linkit.substitution') | |
| + ); | |
| + } | |
| + | |
| + /** | |
| + * Constructs a new LinkitFormatter. | |
| + * | |
| + * @param string $plugin_id | |
| + * The plugin_id for the formatter. | |
| + * @param mixed $plugin_definition | |
| + * The plugin implementation definition. | |
| + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition | |
| + * The definition of the field to which the formatter is associated. | |
| + * @param array $settings | |
| + * The formatter settings. | |
| + * @param string $label | |
| + * The formatter label display setting. | |
| + * @param string $view_mode | |
| + * The view mode. | |
| + * @param array $third_party_settings | |
| + * Third party settings. | |
| + * @param \Drupal\Core\Path\PathValidatorInterface $path_validator | |
| + * The path validator service. | |
| + * @param \Drupal\linkit\SubstitutionManagerInterface $substitution_manager | |
| + * The substitution manager. | |
| + */ | |
| + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, PathValidatorInterface $path_validator, SubstitutionManagerInterface $substitution_manager) { | |
| + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $path_validator); | |
| + $this->substitutionManager = $substitution_manager; | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public static function defaultSettings() { | |
| + return [ | |
| + 'linkit_profile' => 'default', | |
| + ] + parent::defaultSettings(); | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function viewElements(FieldItemListInterface $items, $langcode) { | |
| + $elements = parent::viewElements($items, $langcode); | |
| + | |
| + // Loop over the elements and substitute the URL. | |
| + foreach ($elements as $delta => &$item) { | |
| + /** @var \Drupal\link\LinkItemInterface $link_item */ | |
| + $link_item = $items->get($delta); | |
| + $substituted_url = $this->getSubstitutedUrl($link_item); | |
| + // Convert generated URL into a URL object. | |
| + if ($substituted_url && ($url = \Drupal::pathValidator()->getUrlIfValid($substituted_url->getGeneratedUrl()))) { | |
| + $item['#url'] = $url; | |
| + } | |
| + } | |
| + | |
| + return $elements; | |
| + } | |
| + | |
| + /** | |
| + * Returns a substitution URL for the given linked item. | |
| + * | |
| + * In case the items links to an entity use a substituted/generated URL. | |
| + * | |
| + * @param \Drupal\link\LinkItemInterface $item | |
| + * The link item. | |
| + * | |
| + * @return \Drupal\Core\GeneratedUrl|null | |
| + * The substitution URL, or NULL if not able to retrieve it from the item. | |
| + */ | |
| + protected function getSubstitutedUrl(LinkItemInterface $item) { | |
| + if (parse_url($item->uri, PHP_URL_SCHEME) == 'entity') { | |
| + if ($entity = LinkitHelper::getEntityFromUri($item->uri)) { | |
| + $profile = Profile::load($this->getSettings()['linkit_profile']); | |
| + | |
| + /** @var \\Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher $matcher */ | |
| + $matcher = $profile->getMatcherByEntityType($entity->getEntityTypeId()); | |
| + $substitution_type = $matcher ? $matcher->getConfiguration()['settings']['substitution_type'] : SubstitutionManagerInterface::DEFAULT_SUBSTITUTION; | |
| + return $this->substitutionManager->createInstance($substitution_type)->getUrl($entity); | |
| + } | |
| + } | |
| + | |
| + return NULL; | |
| + } | |
| + | |
| +} | |
| diff --git a/src/Plugin/Field/FieldWidget/LinkitWidget.php b/src/Plugin/Field/FieldWidget/LinkitWidget.php | |
| new file mode 100644 | |
| index 0000000..6822c48 | |
| --- /dev/null | |
| +++ b/src/Plugin/Field/FieldWidget/LinkitWidget.php | |
| @@ -0,0 +1,211 @@ | |
| +<?php | |
| + | |
| +namespace Drupal\linkit\Plugin\Field\FieldWidget; | |
| + | |
| +use Drupal\link\Plugin\Field\FieldWidget\LinkWidget; | |
| +use Drupal\Core\Field\FieldItemListInterface; | |
| +use Drupal\Core\Form\FormStateInterface; | |
| +use Drupal\linkit\Utility\LinkitHelper; | |
| + | |
| +/** | |
| + * Plugin implementation of the 'linkit' widget. | |
| + * | |
| + * @FieldWidget( | |
| + * id = "linkit", | |
| + * label = @Translation("Linkit"), | |
| + * field_types = { | |
| + * "link" | |
| + * } | |
| + * ) | |
| + */ | |
| +class LinkitWidget extends LinkWidget { | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public static function defaultSettings() { | |
| + return [ | |
| + 'linkit_profile' => 'default', | |
| + ] + parent::defaultSettings(); | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + 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' && $entity = LinkitHelper::getEntityFromUri($uri)) { | |
| + // If there is no fragment on the original URI, show the entity label. | |
| + $fragment = parse_url($uri, PHP_URL_FRAGMENT); | |
| + if (empty($fragment)) { | |
| + $displayable_string = $entity->label(); | |
| + } | |
| + } | |
| + | |
| + return $displayable_string; | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public static function validateUriElement($element, FormStateInterface $form_state, $form) { | |
| + if (parse_url($element['#value'], 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; | |
| + } | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { | |
| + $item = $items[$delta]; | |
| + $uri_as_displayable_string = static::getUriAsDisplayableString($item->uri); | |
| + $linkit_profile_id = $this->getSetting('linkit_profile'); | |
| + | |
| + // 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_allowed = !$item->isEmpty() && (\Drupal::currentUser()->hasPermission('link to any page') || $item->getUrl()->access()); | |
| + | |
| + if ($default_allowed && parse_url($item->uri, PHP_URL_SCHEME) == 'entity') { | |
| + $entity = LinkitHelper::getEntityFromUri($item->uri); | |
| + } | |
| + | |
| + $element['uri'] = [ | |
| + '#type' => 'linkit', | |
| + '#title' => $this->t('URL'), | |
| + '#placeholder' => $this->getSetting('placeholder_url'), | |
| + '#default_value' => $default_allowed ? $uri_as_displayable_string : NULL, | |
| + '#element_validate' => [[get_called_class(), 'validateUriElement']], | |
| + '#maxlength' => 2048, | |
| + '#required' => $element['#required'], | |
| + '#description' => $this->t('Start typing to find content or paste a URL and click on the suggestion below.'), | |
| + '#autocomplete_route_name' => 'linkit.autocomplete', | |
| + '#autocomplete_route_parameters' => [ | |
| + 'linkit_profile_id' => $linkit_profile_id, | |
| + ], | |
| + '#error_no_message' => TRUE, | |
| + ]; | |
| + | |
| + $element['attributes']['data-entity-type'] = [ | |
| + '#type' => 'hidden', | |
| + '#default_value' => $default_allowed && isset($entity) ? $entity->getEntityTypeId() : '', | |
| + ]; | |
| + | |
| + $element['attributes']['data-entity-uuid'] = [ | |
| + '#type' => 'hidden', | |
| + '#default_value' => $default_allowed && isset($entity) ? $entity->uuid() : '', | |
| + ]; | |
| + | |
| + $element['attributes']['data-entity-substitution'] = [ | |
| + '#type' => 'hidden', | |
| + '#default_value' => $default_allowed && isset($entity) ? $entity->getEntityTypeId() == 'file' ? 'file' : 'canonical' : '', | |
| + ]; | |
| + | |
| + $element['title'] = [ | |
| + '#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, | |
| + '#attributes' => [ | |
| + 'class' => ['linkit-widget-title'], | |
| + ], | |
| + '#error_no_message' => TRUE, | |
| + ]; | |
| + // 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'][] = [get_called_class(), 'validateTitleElement']; | |
| + } | |
| + | |
| + // 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 += [ | |
| + '#type' => 'fieldset', | |
| + ]; | |
| + } | |
| + } | |
| + | |
| + return $element; | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function settingsForm(array $form, FormStateInterface $form_state) { | |
| + $elements = parent::settingsForm($form, $form_state); | |
| + | |
| + $linkit_profiles = \Drupal::entityTypeManager()->getStorage('linkit_profile')->loadMultiple(); | |
| + | |
| + $options = []; | |
| + foreach ($linkit_profiles as $linkit_profile) { | |
| + $options[$linkit_profile->id()] = $linkit_profile->label(); | |
| + } | |
| + | |
| + $elements['linkit_profile'] = [ | |
| + '#type' => 'select', | |
| + '#title' => $this->t('Linkit profile'), | |
| + '#options' => $options, | |
| + '#default_value' => $this->getSetting('linkit_profile'), | |
| + ]; | |
| + | |
| + return $elements; | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function settingsSummary() { | |
| + $summary = parent::settingsSummary(); | |
| + | |
| + $linkit_profile_id = $this->getSetting('linkit_profile'); | |
| + $linkit_profile = \Drupal::entityTypeManager()->getStorage('linkit_profile')->load($linkit_profile_id); | |
| + | |
| + if ($linkit_profile) { | |
| + $summary[] = $this->t('Linkit profile: @linkit_profile', ['@linkit_profile' => $linkit_profile->label()]); | |
| + } | |
| + | |
| + return $summary; | |
| + } | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { | |
| + foreach ($values as &$value) { | |
| + $value['uri'] = LinkitHelper::getUriFromSubmittedValue($value); | |
| + $value += ['options' => []]; | |
| + } | |
| + return $values; | |
| + } | |
| + | |
| +} | |
| diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php | |
| index a8a85c8..fcce2c3 100644 | |
| --- a/src/Plugin/Linkit/Matcher/EntityMatcher.php | |
| +++ b/src/Plugin/Linkit/Matcher/EntityMatcher.php | |
| @@ -3,6 +3,7 @@ | |
| namespace Drupal\linkit\Plugin\Linkit\Matcher; | |
| use Drupal\Component\Utility\Html; | |
| +use Drupal\Component\Utility\Xss; | |
| use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; | |
| use Drupal\Core\Database\Connection; | |
| use Drupal\Core\Entity\EntityInterface; | |
| diff --git a/src/ProfileInterface.php b/src/ProfileInterface.php | |
| index de8c5da..3d2e8cd 100644 | |
| --- a/src/ProfileInterface.php | |
| +++ b/src/ProfileInterface.php | |
| @@ -38,6 +38,17 @@ interface ProfileInterface extends ConfigEntityInterface { | |
| */ | |
| public function getMatcher($instance_id); | |
| + /** | |
| + * Returns the first enabled matcher for the given entity type ID. | |
| + * | |
| + * @param string $entity_type_id | |
| + * The entity type ID. | |
| + * | |
| + * @return \Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher|null | |
| + * An entity matcher instance or null if not found. | |
| + */ | |
| + public function getMatcherByEntityType($entity_type_id); | |
| + | |
| /** | |
| * Returns the matchers for this profile. | |
| * | |
| diff --git a/src/Utility/LinkitHelper.php b/src/Utility/LinkitHelper.php | |
| new file mode 100644 | |
| index 0000000..ef17ae0 | |
| --- /dev/null | |
| +++ b/src/Utility/LinkitHelper.php | |
| @@ -0,0 +1,82 @@ | |
| +<?php | |
| + | |
| +namespace Drupal\linkit\Utility; | |
| + | |
| +/** | |
| + * Provides helper to operate on URIs. | |
| + */ | |
| +class LinkitHelper { | |
| + | |
| + /** | |
| + * Load the entity referenced by an entity scheme uri. | |
| + * | |
| + * @param string $uri | |
| + * An internal uri string representing an entity path, such as | |
| + * "entity:node/23". | |
| + * | |
| + * @return \Drupal\Core\Entity\EntityInterface|null | |
| + * The most appropriate translation of the entity that matches the given | |
| + * uri, or NULL if could not match any entity. | |
| + */ | |
| + public static function getEntityFromUri($uri) { | |
| + // Stripe out potential query and fragment from the uri. | |
| + $uri = strtok(strtok($uri, "?"), "#"); | |
| + list($entity_type, $entity_id) = explode('/', substr($uri, 7), 2); | |
| + $entity_manager = \Drupal::entityTypeManager(); | |
| + if ($entity_manager->getDefinition($entity_type, FALSE)) { | |
| + if ($entity = $entity_manager->getStorage($entity_type)->load($entity_id)) { | |
| + return \Drupal::service('entity.repository')->getTranslationFromContext($entity); | |
| + } | |
| + } | |
| + | |
| + return NULL; | |
| + } | |
| + | |
| + /** | |
| + * Converts linkit form fields to a uri. | |
| + * | |
| + * @param array $value | |
| + * User submitted values for this widget. | |
| + * | |
| + * @return string | |
| + * An internal uri string, such as "internal:blog" or "entity:node/23". | |
| + */ | |
| + public static function getUriFromSubmittedValue(array $value) { | |
| + $uri = $value['uri']; | |
| + | |
| + if (empty($uri)) { | |
| + return ''; | |
| + } | |
| + | |
| + if (!empty($value['attributes']['data-entity-type']) && !empty($value['attributes']['data-entity-uuid'])) { | |
| + $entity_type = $value['attributes']['data-entity-type']; | |
| + $entity_uuid = $value['attributes']['data-entity-uuid']; | |
| + | |
| + /* @var \Drupal\Core\Entity\EntityInterface $entity */ | |
| + $entity = \Drupal::service('entity.repository')->loadEntityByUuid($entity_type, $entity_uuid); | |
| + if ($entity) { | |
| + $entity_uri = 'entity:' . $entity->getEntityTypeId() . '/' . $entity->id(); | |
| + // Preserve the fragment, if present. | |
| + $fragment = parse_url($uri, PHP_URL_FRAGMENT); | |
| + if (!empty($fragment)) { | |
| + $entity_uri .= '#' . $fragment; | |
| + } | |
| + return $entity_uri; | |
| + } | |
| + } | |
| + | |
| + if (!empty($uri) && parse_url($uri, 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($uri, '<front>') === 0) { | |
| + $uri = '/' . substr($uri, strlen('<front>')); | |
| + } | |
| + return 'internal:' . $uri; | |
| + } | |
| + | |
| + return $uri; | |
| + } | |
| + | |
| +} | |
| diff --git a/tests/src/FunctionalJavascript/LinkFieldTest.php b/tests/src/FunctionalJavascript/LinkFieldTest.php | |
| new file mode 100644 | |
| index 0000000..6216d0a | |
| --- /dev/null | |
| +++ b/tests/src/FunctionalJavascript/LinkFieldTest.php | |
| @@ -0,0 +1,235 @@ | |
| +<?php | |
| + | |
| +namespace Drupal\Tests\linkit\FunctionalJavascript; | |
| + | |
| +use Drupal\entity_test\Entity\EntityTestMul; | |
| +use Drupal\field\Entity\FieldConfig; | |
| +use Drupal\field\Entity\FieldStorageConfig; | |
| +use Drupal\FunctionalJavascriptTests\JavascriptTestBase; | |
| +use Drupal\linkit\Tests\ProfileCreationTrait; | |
| +use Drupal\node\Entity\NodeType; | |
| + | |
| +/** | |
| + * Tests the widget and formatter for Link fields. | |
| + * | |
| + * @group linkit | |
| + */ | |
| +class LinkFieldTest extends JavascriptTestBase { | |
| + | |
| + use ProfileCreationTrait; | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + public static $modules = [ | |
| + 'node', | |
| + 'language', | |
| + 'field_ui', | |
| + 'entity_test', | |
| + 'link', | |
| + 'linkit', | |
| + ]; | |
| + | |
| + /** | |
| + * A linkit profile. | |
| + * | |
| + * @var \Drupal\linkit\ProfileInterface | |
| + */ | |
| + protected $linkitProfile; | |
| + | |
| + /** | |
| + * {@inheritdoc} | |
| + */ | |
| + protected function setUp() { | |
| + parent::setUp(); | |
| + | |
| + $matcherManager = $this->container->get('plugin.manager.linkit.matcher'); | |
| + /** @var \Drupal\linkit\MatcherInterface $plugin */ | |
| + | |
| + $this->linkitProfile = $this->createProfile(); | |
| + $plugin = $matcherManager->createInstance('entity:entity_test_mul'); | |
| + $this->linkitProfile->addMatcher($plugin->getConfiguration()); | |
| + $this->linkitProfile->save(); | |
| + | |
| + // Create a node type for testing. | |
| + NodeType::create(['type' => 'page', 'name' => 'page'])->save(); | |
| + | |
| + // Create a link field. | |
| + $storage = FieldStorageConfig::create([ | |
| + 'field_name' => 'field_test_link', | |
| + 'entity_type' => 'node', | |
| + 'type' => 'link', | |
| + ]); | |
| + $storage->save(); | |
| + FieldConfig::create([ | |
| + 'bundle' => 'page', | |
| + 'entity_type' => 'node', | |
| + 'field_name' => 'field_test_link', | |
| + ])->save(); | |
| + | |
| + // Define our widget and formatter for this field. | |
| + entity_get_form_display('node', 'page', 'default') | |
| + ->setComponent('field_test_link', [ | |
| + 'type' => 'linkit', | |
| + ]) | |
| + ->save(); | |
| + entity_get_display('node', 'page', 'default') | |
| + ->setComponent('field_test_link', [ | |
| + 'type' => 'linkit', | |
| + ]) | |
| + ->save(); | |
| + | |
| + $account = $this->drupalCreateUser([ | |
| + 'administer node fields', | |
| + 'administer node display', | |
| + 'administer nodes', | |
| + 'bypass node access', | |
| + 'view test entity', | |
| + ]); | |
| + | |
| + $this->drupalLogin($account); | |
| + } | |
| + | |
| + /** | |
| + * Test the "linkit" widget and formatter. | |
| + */ | |
| + public function testLinkFieldWidgetAndFormatter() { | |
| + $session = $this->getSession(); | |
| + $assert_session = $this->assertSession(); | |
| + $page = $session->getPage(); | |
| + | |
| + // Create a test entity to be used as target. | |
| + /** @var \Drupal\Core\Entity\EntityInterface $entity */ | |
| + $entity = EntityTestMul::create(['name' => 'Foo']); | |
| + $entity->save(); | |
| + | |
| + // Test the widget behavior. | |
| + $this->drupalGet('node/add/page'); | |
| + | |
| + $assert_session->elementContains('css', '#edit-field-test-link-wrapper', 'Start typing to find content or paste a URL and click on the suggestion below.'); | |
| + $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper'); | |
| + $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $uri_input->setValue('f'); | |
| + $session->getDriver()->keyDown($uri_input->getXpath(), ' '); | |
| + $assert_session->waitOnAutocomplete(); | |
| + | |
| + // With the default profile no results are found. | |
| + $autocomplete_results_wrapper = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete'); | |
| + $this->assertTrue($autocomplete_results_wrapper->isVisible()); | |
| + $result_description = $assert_session->elementExists('css', 'li.linkit-result-line .linkit-result-line--description', $autocomplete_results_wrapper); | |
| + $this->assertEquals('Linkit could not find any suggestions. This URL will be used as is.', $result_description->getText()); | |
| + | |
| + // Set the widget to use our profile and try again. | |
| + entity_get_form_display('node', 'page', 'default') | |
| + ->setComponent('field_test_link', [ | |
| + 'type' => 'linkit', | |
| + 'settings' => [ | |
| + 'linkit_profile' => $this->linkitProfile->id(), | |
| + ], | |
| + ]) | |
| + ->save(); | |
| + $this->drupalGet('node/add/page'); | |
| + $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper'); | |
| + $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $uri_input->setValue('f'); | |
| + $session->getDriver()->keyDown($uri_input->getXpath(), 'o'); | |
| + $assert_session->waitOnAutocomplete(); | |
| + $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title'); | |
| + $first_result->click(); | |
| + $assert_session->assertWaitOnAjaxRequest(); | |
| + | |
| + // Check that the URL input field value shows the entity label. | |
| + $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $this->assertEquals('Foo', $url_input->getValue()); | |
| + // Check that the title was populated automatically. | |
| + $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper); | |
| + $this->assertEquals('Foo', $title_input->getValue()); | |
| + | |
| + // Give the node a title and save the page. | |
| + $page->fillField('title[0][value]', 'Host test node 1'); | |
| + $page->pressButton('Save'); | |
| + $assert_session->pageTextContains('Host test node 1 has been created'); | |
| + | |
| + // Check that we are viewing the node, and the formatter displays what we | |
| + // expect. | |
| + $assert_session->titleEquals('Host test node 1 | Drupal'); | |
| + $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link'); | |
| + $link_element = $assert_session->elementExists('css', 'a', $field_wrapper); | |
| + $this->assertEquals('Foo', $link_element->getText()); | |
| + $href_value = $link_element->getAttribute('href'); | |
| + $this->assertContains("/entity_test_mul/manage/{$entity->id()}", $href_value); | |
| + | |
| + // Test internal entity targets with anchors. | |
| + /** @var \Drupal\Core\Entity\EntityInterface $entity */ | |
| + $entity2 = EntityTestMul::create(['name' => 'Anchored Entity']); | |
| + $entity2->save(); | |
| + | |
| + // Test the widget behavior. | |
| + $this->drupalGet('node/add/page'); | |
| + | |
| + $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper'); | |
| + $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $uri_input->setValue('Anchored'); | |
| + $session->getDriver()->keyDown($uri_input->getXpath(), ' '); | |
| + $assert_session->waitOnAutocomplete(); | |
| + $first_result = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete li.linkit-result-line span.linkit-result-line--title'); | |
| + $first_result->click(); | |
| + $assert_session->assertWaitOnAjaxRequest(); | |
| + | |
| + // Check that the URL input field value shows the entity label. | |
| + $url_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $this->assertEquals('Anchored Entity', $url_input->getValue()); | |
| + // Check that the title was populated automatically. | |
| + $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper); | |
| + $this->assertEquals('Anchored Entity', $title_input->getValue()); | |
| + | |
| + // Add an anchor to the URL field. | |
| + $url_input->setValue('Anchored Entity#with-anchor'); | |
| + | |
| + // Give the node a title and save the page. | |
| + $page->fillField('title[0][value]', 'Host test node 2'); | |
| + $page->pressButton('Save'); | |
| + $assert_session->pageTextContains('Host test node 2 has been created'); | |
| + | |
| + // Check that we are viewing the node, and the formatter displays what we | |
| + // expect. | |
| + $assert_session->titleEquals('Host test node 2 | Drupal'); | |
| + $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link'); | |
| + $link_element = $assert_session->elementExists('css', 'a', $field_wrapper); | |
| + $this->assertEquals('Anchored Entity', $link_element->getText()); | |
| + $href_value = $link_element->getAttribute('href'); | |
| + $this->assertContains("/entity_test_mul/manage/{$entity2->id()}#with-anchor", $href_value); | |
| + | |
| + // Test external URLs. | |
| + $this->drupalGet('node/add/page'); | |
| + | |
| + $widget_wrapper = $assert_session->elementExists('css', '#edit-field-test-link-wrapper'); | |
| + $uri_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][uri]"]', $widget_wrapper); | |
| + $uri_input->setValue('https://google.com#foobar'); | |
| + $session->getDriver()->keyDown($uri_input->getXpath(), ' '); | |
| + $assert_session->waitOnAutocomplete(); | |
| + $autocomplete_results_wrapper = $assert_session->elementExists('css', 'ul.linkit-ui-autocomplete'); | |
| + $this->assertTrue($autocomplete_results_wrapper->isVisible()); | |
| + $result_description = $assert_session->elementExists('css', 'li.linkit-result-line .linkit-result-line--description', $autocomplete_results_wrapper); | |
| + $this->assertEquals('Linkit could not find any suggestions. This URL will be used as is.', $result_description->getText()); | |
| + | |
| + // Set a manual value for the title. | |
| + $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper); | |
| + $title_input->setValue('This is google'); | |
| + | |
| + // Give the node a title and save the page. | |
| + $page->fillField('title[0][value]', 'Host test node 3'); | |
| + $page->pressButton('Save'); | |
| + $assert_session->pageTextContains('Host test node 3 has been created'); | |
| + | |
| + // Check that we are viewing the node, and the formatter displays what we | |
| + // expect. | |
| + $assert_session->titleEquals('Host test node 3 | Drupal'); | |
| + $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link'); | |
| + $link_element = $assert_session->elementExists('css', 'a', $field_wrapper); | |
| + $this->assertEquals('This is google', $link_element->getText()); | |
| + $href_value = $link_element->getAttribute('href'); | |
| + $this->assertContains('https://google.com#foobar', $href_value); | |
| + } | |
| + | |
| +} | |
| diff --git a/tests/src/FunctionalJavascript/LinkitDialogTest.php b/tests/src/FunctionalJavascript/LinkitDialogTest.php | |
| index dfad7ab..fbf57c6 100644 | |
| --- a/tests/src/FunctionalJavascript/LinkitDialogTest.php | |
| +++ b/tests/src/FunctionalJavascript/LinkitDialogTest.php | |
| @@ -190,8 +190,8 @@ class LinkitDialogTest extends JavascriptTestBase { | |
| // Find the first result and click it. | |
| $page->find('xpath', '//li[contains(@class, "linkit-result-line") and contains(@class, "ui-menu-item")][1]')->click(); | |
| - // Make sure the linkit field field is populated with the node url. | |
| - $this->assertEquals($entity->toUrl()->toString(), $href_field->getValue(), 'The href field is populated with the node url.'); | |
| + // Make sure the linkit field field is populated with the node label. | |
| + $this->assertEquals($entity->label(), $href_field->getValue(), 'The href field was not populated with the node label.'); | |
| // Make sure all other fields are populated. | |
| $this->assertEqualsWithJs('attributes[data-entity-type]', $entity->getEntityTypeId()); | |
| diff --git a/tests/src/Kernel/LinkitEditorLinkDialogTest.php b/tests/src/Kernel/LinkitEditorLinkDialogTest.php | |
| index 0a1963b..42504fc 100644 | |
| --- a/tests/src/Kernel/LinkitEditorLinkDialogTest.php | |
| +++ b/tests/src/Kernel/LinkitEditorLinkDialogTest.php | |
| @@ -128,7 +128,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase { | |
| $form_state->setValue(['attributes', 'href'], 'https://example.com/'); | |
| $form_state->setValue('href_dirty_check', ''); | |
| - $form_state->setValue(['attributes', 'data-entity-type'], $this->randomString()); | |
| + $form_state->setValue(['attributes', 'data-entity-type'], $entity->getEntityTypeId()); | |
| $form_state->setValue(['attributes', 'data-entity-uuid'], $this->randomString()); | |
| $form_state->setValue(['attributes', 'data-entity-substitution'], $this->randomString()); | |
| $form_builder->submitForm($form_object, $form_state); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment