diff --git a/composer.json b/composer.json index 97c8aa6d698460d4a3c36e205cd4ccbf8e1c73d2..9b8fa8c6304ba73949d74676d65e2fb13e35ca6e 100644 --- a/composer.json +++ b/composer.json @@ -8,35 +8,35 @@ "url": "https://packages.drupal.org/8" }, { - "type": "package", - "package": { - "name": "browserstate/history.js", - "version": "1.8.0", - "type": "drupal-library", - "source": { - "url": "https://github.com/browserstate/history.js", - "type": "git", - "reference": "origin/master" - } - } + "type": "package", + "package": { + "name": "browserstate/history.js", + "version": "1.8.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/browserstate/history.js", + "type": "git", + "reference": "origin/master" + } + } }, { - "type": "package", - "package": { - "name": "ckeditor/indentblock", - "version": "4.8.0", - "type": "drupal-library", - "extra": { - "installer-name": "indentblock" - }, - "dist": { - "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip", - "type": "zip" - }, - "require": { - "composer/installers": "~1.0" - } - } + "type": "package", + "package": { + "name": "ckeditor/indentblock", + "version": "4.8.0", + "type": "drupal-library", + "extra": { + "installer-name": "indentblock" + }, + "dist": { + "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } }, { "type": "package", @@ -52,16 +52,16 @@ } }, { - "type": "package", - "package": { - "name": "desandro/masonry", - "version": "4.2.0", - "type": "drupal-library", - "dist": { - "url": "https://github.com/desandro/masonry/archive/v4.2.0.zip", - "type": "zip" - } - } + "type": "package", + "package": { + "name": "desandro/masonry", + "version": "4.2.0", + "type": "drupal-library", + "dist": { + "url": "https://github.com/desandro/masonry/archive/v4.2.0.zip", + "type": "zip" + } + } }, { "type": "package", @@ -241,17 +241,17 @@ "drupal-scaffold": { "source": "https://raw.githubusercontent.com/pantheon-systems/drops-8-scaffolding/{version}/{path}", "locations": { - "web-root": "web/" + "web-root": "web/" }, "file-mapping": { - "[web-root]/INSTALL.txt": false, - "[web-root]/README.txt": false, - "[web-root]/example.gitignore": false, - "[web-root]/modules/README.txt": false, - "[web-root]/profiles/README.txt": false, - "[web-root]/sites/README.txt": false, - "[web-root]/themes/README.txt": false, - "[web-root]/web.config": false + "[web-root]/INSTALL.txt": false, + "[web-root]/README.txt": false, + "[web-root]/example.gitignore": false, + "[web-root]/modules/README.txt": false, + "[web-root]/profiles/README.txt": false, + "[web-root]/sites/README.txt": false, + "[web-root]/themes/README.txt": false, + "[web-root]/web.config": false } }, "patches": { @@ -270,10 +270,10 @@ "2811189": "https://www.drupal.org/files/issues/2019-08-08/honeypot_field_weight_2811189-18.patch" }, "drupal/inline_entity_form": { - "3208279": "https://www.drupal.org/files/issues/2021-05-08/inline_entity_form-n3208279-13.patch" + "3208279": "https://www.drupal.org/files/issues/2021-05-08/inline_entity_form-n3208279-13.patch" }, "drupal/linkit": { - "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch" + "2712951": "https://www.drupal.org/files/issues/2021-04-07/linkit-for-link-field-2712951-216.patch" }, "drupal/multiple_fields_remove_button": { "Adding Date Range": "patches/multiple_fields_remove_button-daterange-addition.patch" @@ -297,4 +297,4 @@ "php": "7.3" } } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 8c56468a3d229e92ed52dc7cda106128c1731dfb..a6998f9407383128cfe3cd6be3baee4d12c68aa9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57672c9e8b696e724095b806e2f77021", + "content-hash": "b66a2b43b306587d15fdf11ea2858c8e", "packages": [ { "name": "alchemy/zippy", diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 17e2162afed14560c5ce505052a68615faf8e6b9..d10cf1dfe638af1c3084458ed964d208e446c1b7 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -32,7 +32,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '0f8a8ac02e1ff090c2405d8b83248b5f4f75856b', + 'reference' => 'a971e84acff6246067d76e462156e6664731e864', 'name' => 'osu-asc-webservices/d8-upstream', ), 'versions' => @@ -2262,7 +2262,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '0f8a8ac02e1ff090c2405d8b83248b5f4f75856b', + 'reference' => 'a971e84acff6246067d76e462156e6664731e864', ), 'pantheon-systems/quicksilver-pushback' => array ( @@ -3281,8 +3281,11 @@ public static function getRootPackage() + public static function getRawData() { +@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + return self::$installed; } @@ -3292,6 +3295,17 @@ public static function getRawData() +public static function getAllRawData() +{ +return self::getInstalled(); +} + + + + + + + diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index b15055f50a27c47d987d5b01b5c8a38ab479dd46..4d3c1db90ad69f89b5c3a45bc5be757306f418a2 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -5513,7 +5513,7 @@ } }, "patches_applied": { - "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch" + "2712951": "https://www.drupal.org/files/issues/2021-04-07/linkit-for-link-field-2712951-216.patch" } }, "installation-source": "dist", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 447187c225e90628dc08014de6f557344ecf0f38..3616e8059dec3af568f6a1a8b21c7261b86c3851 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => '0f8a8ac02e1ff090c2405d8b83248b5f4f75856b', + 'reference' => 'a971e84acff6246067d76e462156e6664731e864', 'name' => 'osu-asc-webservices/d8-upstream', ), 'versions' => @@ -2236,7 +2236,7 @@ 'aliases' => array ( ), - 'reference' => '0f8a8ac02e1ff090c2405d8b83248b5f4f75856b', + 'reference' => 'a971e84acff6246067d76e462156e6664731e864', ), 'pantheon-systems/quicksilver-pushback' => array ( diff --git a/web/modules/linkit/PATCHES.txt b/web/modules/linkit/PATCHES.txt index 2c2692ba419ab1ae53ae6423c71d699d21bbbad6..ad45756bcc49243ecb529af58a6e4b22ca371ea8 100644 --- a/web/modules/linkit/PATCHES.txt +++ b/web/modules/linkit/PATCHES.txt @@ -2,6 +2,6 @@ This file was automatically generated by Composer Patches (https://github.com/cw Patches applied to this directory: 2712951 -Source: https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch +Source: https://www.drupal.org/files/issues/2021-04-07/linkit-for-link-field-2712951-216.patch diff --git a/web/modules/linkit/config/schema/linkit.schema.yml b/web/modules/linkit/config/schema/linkit.schema.yml index b8d487cbd0d1851e115a1b30dd5ddec1ddb2ac7e..3787e95c9abca4a8bcac582ee501287bca102c0f 100644 --- a/web/modules/linkit/config/schema/linkit.schema.yml +++ b/web/modules/linkit/config/schema/linkit.schema.yml @@ -121,6 +121,9 @@ field.widget.settings.linkit: linkit_profile: type: string label: 'Linkit profile' + linkit_auto_link_text: + type: boolean + label: 'Automatically populate link text from entity label' # Schema for the Linkit formatter. field.formatter.settings.linkit: diff --git a/web/modules/linkit/js/linkit.autocomplete.js b/web/modules/linkit/js/linkit.autocomplete.js index b2ccca311d0b6a330fbd650a2ee2d27c40c70065..8ef722fe6000569b1f114281f85fa40064248cb5 100644 --- a/web/modules/linkit/js/linkit.autocomplete.js +++ b/web/modules/linkit/js/linkit.autocomplete.js @@ -83,7 +83,7 @@ if (ui.item.label) { // Automatically set the link title. - var $linkTitle = $(event.target).closest('.form-item').siblings('.form-type-textfield').find('.linkit-widget-title'); + var $linkTitle = $('.linkit-widget-title--autofill-enabled', $context); if ($linkTitle.length > 0) { if (!$linkTitle.val() || $linkTitle.hasClass('link-widget-title--auto')) { // Set value to the label. diff --git a/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php b/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php index 775da51423b9a224168f660d9a9b15d035c9055b..c1070349b7f980a706c4a7a09346f417cedc9041 100644 --- a/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php +++ b/web/modules/linkit/src/Plugin/Field/FieldFormatter/LinkitFormatter.php @@ -2,12 +2,9 @@ namespace Drupal\linkit\Plugin\Field\FieldFormatter; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Path\PathValidatorInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\BubbleableMetadata; use Drupal\link\LinkItemInterface; use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter; use Drupal\linkit\Entity\Profile; @@ -26,7 +23,7 @@ * } * ) */ -class LinkitFormatter extends LinkFormatter implements ContainerFactoryPluginInterface { +class LinkitFormatter extends LinkFormatter { /** * The substitution manager. @@ -46,48 +43,10 @@ class LinkitFormatter extends LinkFormatter implements ContainerFactoryPluginInt * {@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'), - $container->get('entity_type.manager') - ); - } - - /** - * 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. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type 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, EntityTypeManagerInterface $entity_type_manager) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $path_validator); - $this->substitutionManager = $substitution_manager; - $this->entityTypeManager = $entity_type_manager; + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->substitutionManager = $container->get('plugin.manager.linkit.substitution'); + $instance->entityTypeManager = $container->get('entity_type.manager'); + return $instance; } /** @@ -152,6 +111,27 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $substituted_url = $this->getSubstitutedUrl($link_item); // Convert generated URL into a URL object. if ($substituted_url && ($url = \Drupal::pathValidator()->getUrlIfValid($substituted_url->getGeneratedUrl()))) { + // Keep query and fragment. + $parsed_url = parse_url($link_item->uri); + if (!empty($parsed_url['query'])) { + $parsed_query = []; + parse_str($parsed_url['query'], $parsed_query); + if (!empty($parsed_query)) { + $url->setOption('query', $parsed_query); + } + } + if (!empty($parsed_url['fragment'])) { + $url->setOption('fragment', $parsed_url['fragment']); + } + // Add cache dependency to the generated substituted URL. + $cacheable_metadata = BubbleableMetadata::createFromRenderArray($item) + ->addCacheableDependency($substituted_url); + // Add cache dependency to the referenced entity, e.g. for media direct + // file substitution. + if ($entity = LinkitHelper::getEntityFromUserInput($link_item->uri)) { + $cacheable_metadata->addCacheableDependency($entity); + } + $cacheable_metadata->applyTo($item); $item['#url'] = $url; } } @@ -171,15 +151,13 @@ public function viewElements(FieldItemListInterface $items, $langcode) { * 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); - } + if ($entity = LinkitHelper::getEntityFromUserInput($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/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php b/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php index d2f7823bfb14d1f967ff55df606a3eb42d451c6a..f67867e7e524c3701076ddbbec03741699e6c4e7 100644 --- a/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php +++ b/web/modules/linkit/src/Plugin/Field/FieldWidget/LinkitWidget.php @@ -27,6 +27,7 @@ class LinkitWidget extends LinkWidget { public static function defaultSettings() { return [ 'linkit_profile' => 'default', + 'linkit_auto_link_text' => FALSE, ] + parent::defaultSettings(); } @@ -37,11 +38,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $item = $items[$delta]; $uri = $item->uri; $uri_scheme = parse_url($uri, PHP_URL_SCHEME); - if (!empty($uri) && empty($uri_scheme)) { + $is_nolink = substr($uri, 0, 14) === 'route:<nolink>'; + if (!empty($uri) && empty($uri_scheme) && $is_nolink) { $uri = LinkitHelper::uriFromUserInput($uri); $uri_scheme = parse_url($uri, PHP_URL_SCHEME); } - $uri_as_url = !empty($uri) ? Url::fromUri($uri)->toString() : ''; + if ($is_nolink) { + $uri_as_url = $uri; + } + else { + $uri_as_url = !empty($uri) ? static::getUriAsDisplayableString($uri) : ''; + } $linkit_profile_id = $this->getSetting('linkit_profile'); // The current field value could have been entered by a different user. @@ -95,11 +102,15 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL, '#maxlength' => 255, '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED, + '#required' => $this->getFieldSetting('title') === DRUPAL_REQUIRED && $element['#required'], '#attributes' => [ 'class' => ['linkit-widget-title'], ], '#error_no_message' => TRUE, ]; + if ($this->getSetting('linkit_auto_link_text')) { + $element['title']['#attributes']['class'][] = 'linkit-widget-title--autofill-enabled'; + } // 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. @@ -144,6 +155,11 @@ public function settingsForm(array $form, FormStateInterface $form_state) { '#options' => $options, '#default_value' => $this->getSetting('linkit_profile'), ]; + $elements['linkit_auto_link_text'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Automatically populate link text from entity label'), + '#default_value' => $this->getSetting('linkit_auto_link_text'), + ]; return $elements; } @@ -161,6 +177,12 @@ public function settingsSummary() { $summary[] = $this->t('Linkit profile: @linkit_profile', ['@linkit_profile' => $linkit_profile->label()]); } + $auto_link_text = $this->getSetting('linkit_auto_link_text') ? $this->t('Yes') : $this->t('No'); + $summary[] = $this->t( + 'Automatically populate link text from entity label: @auto_link_text', + ['@auto_link_text' => $auto_link_text] + ); + return $summary; } @@ -175,4 +197,20 @@ public function massageFormValues(array $values, array $form, FormStateInterface return $values; } + /** + * {@inheritdoc} + */ + protected static function getUriAsDisplayableString($uri) { + $scheme = parse_url($uri, PHP_URL_SCHEME); + if ($scheme === 'base') { + $uri_reference = explode(':', $uri, 2)[1]; + $uri = 'internal:' . $uri_reference; + } + elseif ($scheme === 'entity') { + $uri_reference = explode(':', $uri, 2)[1]; + $uri = '/' . $uri_reference; + } + return parent::getUriAsDisplayableString($uri); + } + } diff --git a/web/modules/linkit/src/Plugin/Linkit/Matcher/NolinkMatcher.php b/web/modules/linkit/src/Plugin/Linkit/Matcher/NolinkMatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..90eee020b40ee45e700b38f54527c2d9f47f9acb --- /dev/null +++ b/web/modules/linkit/src/Plugin/Linkit/Matcher/NolinkMatcher.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\linkit\Plugin\Linkit\Matcher; + +use Drupal\linkit\MatcherBase; +use Drupal\linkit\Suggestion\DescriptionSuggestion; +use Drupal\linkit\Suggestion\SuggestionCollection; + +/** + * Provides a linkit matcher for route:<nolink>. + * + * @Matcher( + * id = "nolink", + * label = @Translation("Nolink"), + * ) + */ +class NolinkMatcher extends MatcherBase { + + /** + * {@inheritdoc} + */ + public function execute($string) { + $suggestions = new SuggestionCollection(); + + // Check for the text 'nolink' (e.g. like route:<nolink> with core link + // fields) and return route:<nolink> if it exists. + if (strpos($string, 'nolink') !== FALSE) { + $suggestion = new DescriptionSuggestion(); + $suggestion->setLabel($this->t('Empty link')) + ->setPath('route:<nolink>') + ->setGroup($this->t('System')) + ->setDescription($this->t('An empty link')); + + $suggestions->addSuggestion($suggestion); + } + return $suggestions; + } + +} diff --git a/web/modules/linkit/src/Utility/LinkitHelper.php b/web/modules/linkit/src/Utility/LinkitHelper.php index 56e9fab5bcb955600b9742127e78d5472f84e5c9..8376ca4048a3e2f1190eccfeae8a517b981537aa 100644 --- a/web/modules/linkit/src/Utility/LinkitHelper.php +++ b/web/modules/linkit/src/Utility/LinkitHelper.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; +use Drupal\Core\Entity\EntityInterface; /** * Provides helper to operate on URIs. @@ -24,11 +25,23 @@ class LinkitHelper { 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); + // Remove the schema, if any. Otherwise, remove the forwarding "/". + if (strpos($uri, 'entity:') !== FALSE) { + list(, $uri) = explode(':', $uri); + } + else { + $uri = trim($uri, '/'); + } + + if ($uri) { + $parts = explode('/', $uri, 2); + if (count($parts) == 2 && ($entity_type = $parts[0]) && ($entity_id = $parts[1])) { + $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); + } + } } } @@ -58,7 +71,7 @@ public static function uriFromUserInput($input) { return $input; } - if (UrlHelper::isExternal($input)) { + if ($host && UrlHelper::isExternal($input)) { if (UrlHelper::externalIsLocal($input, \Drupal::request()->getSchemeAndHttpHost())) { // The link points to this domain. Make it relative to perform an entity // lookup. @@ -73,11 +86,18 @@ public static function uriFromUserInput($input) { // Make sure the URI starts with a slash, otherwise the Url's factory // methods will throw exceptions. + $starts_with_hash = strpos($input, '#') === 0; $starts_with_a_slash = strpos($input, '/') === 0; $is_front = substr($input, 0, 7) === '<front>'; - if (!$scheme && !$is_front && !$starts_with_a_slash) { + $is_nolink = substr($input, 0, 14) === 'route:<nolink>'; + if (!$scheme && !$is_front && !$is_nolink && !$starts_with_a_slash && !$starts_with_hash) { $input = "/$input"; } + // - '<front>' -> '/' + // - '<front>#foo' -> '/#foo' + if ($is_front) { + $input = '/' . substr($input, strlen('<front>')); + } $entity = self::getEntityFromUserInput($input); if ($entity) { @@ -89,9 +109,15 @@ public static function uriFromUserInput($input) { $public_files_dir = \Drupal::service('stream_wrapper_manager') ->getViaScheme('public') ->getDirectoryPath(); - if (strpos($input, "/$public_files_dir") === 0) { + + $protocol_matches = []; + preg_match('/^([a-z]*?):/', $input, $protocol_matches); + if (!empty($public_files_dir) && strpos($input, "/$public_files_dir") === 0) { return "base:$input"; } + elseif ((count($protocol_matches) > 1 && in_array($protocol_matches[1], UrlHelper::getAllowedProtocols())) || $is_nolink) { + return $input; + } else { return "internal:$input"; } @@ -110,7 +136,7 @@ public static function getEntityFromUserInput($input) { $scheme = parse_url($input, PHP_URL_SCHEME); // Check if it's an entity URI (e.g. entity:node/1). - if ($scheme === 'entity' && ($entity = static::getEntityFromUri($input))) { + if (($scheme === 'entity' || !$scheme) && ($entity = static::getEntityFromUri($input))) { return $entity; } @@ -122,12 +148,16 @@ public static function getEntityFromUserInput($input) { try { $route_name = Url::fromUri($input)->getRouteName(); - if ($route_name != "entity.node.edit_form" && $route_name != "node.add") { - $params = Url::fromUri($input)->getRouteParameters(); - $possibly_an_entity_type = key($params); + $params = array_filter(Url::fromUri($input)->getRouteParameters()); + $possibly_an_entity_type = key($params); + // Return only the entity, if this is a canonical route. + if ($route_name === 'entity.' . $possibly_an_entity_type . '.canonical') { $entity = \Drupal::entityTypeManager() ->getStorage($possibly_an_entity_type) ->load($params[$possibly_an_entity_type]); + if (!($entity instanceof EntityInterface)) { + return NULL; + } return \Drupal::service('entity.repository') ->getTranslationFromContext($entity); } @@ -149,19 +179,20 @@ public static function getEntityFromUserInput($input) { * The internal path if any matched. The input string otherwise. */ public static function getPathByAlias($input) { - $prefixes = \Drupal::config('language.negotiation')->get('url.prefixes'); - /** @var \Drupal\Core\Path\AliasManagerInterface $path_alias_manager */ - $path_alias_manager = \Drupal::service('path.alias_manager'); + $config = \Drupal::config('language.negotiation'); + /** @var \Drupal\path_alias\AliasManagerInterface $path_alias_manager */ + $path_alias_manager = \Drupal::service('path_alias.manager'); /** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */ $language_manager = \Drupal::service('language_manager'); foreach ($language_manager->getLanguages() as $language) { - $prefix = $prefixes[$language->getId()]; - // Strip the language prefix. - $initial_path = parse_url($input, PHP_URL_PATH); - $path_without_prefix = preg_replace("/^\/$prefix\//", '/', $initial_path); - $path_resolved = $path_alias_manager->getPathByAlias($path_without_prefix, $language->getId()); - if ($path_resolved !== $path_without_prefix) { + $input_path = parse_url($input, PHP_URL_PATH); + if ($prefix = $config->get('url.prefixes.' . $language->getId())) { + // Strip the language prefix. + $input_path = preg_replace("/^\/$prefix\//", '/', $input_path); + } + $path_resolved = $path_alias_manager->getPathByAlias($input_path, $language->getId()); + if ($path_resolved !== $input_path) { return $path_resolved . static::getQueryAndFragment($input); } } diff --git a/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php b/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php index de5343892e5a31a55598b54a50a4d97e394b4dce..b5a591072c886fe94055a6e26e368bd6630aa3bd 100644 --- a/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php +++ b/web/modules/linkit/tests/src/FunctionalJavascript/LinkFieldTest.php @@ -30,6 +30,11 @@ class LinkFieldTest extends WebDriverTestBase { 'linkit', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A linkit profile. * @@ -43,6 +48,8 @@ class LinkFieldTest extends WebDriverTestBase { protected function setUp() { parent::setUp(); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entityDisplayRepository */ + $entityDisplayRepository = $this->container->get('entity_display.repository'); $matcherManager = $this->container->get('plugin.manager.linkit.matcher'); /** @var \Drupal\linkit\MatcherInterface $plugin */ @@ -72,12 +79,12 @@ protected function setUp() { ])->save(); // Define our widget and formatter for this field. - entity_get_form_display('node', 'page', 'default') + $this->container->get('entity_display.repository')->getFormDisplay('node', 'page', 'default') ->setComponent('field_test_link', [ 'type' => 'linkit', ]) ->save(); - entity_get_display('node', 'page', 'default') + $entityDisplayRepository->getViewDisplay('node', 'page', 'default') ->setComponent('field_test_link', [ 'type' => 'linkit', ]) @@ -123,12 +130,14 @@ public function testLinkFieldWidgetAndFormatter() { $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') + // Set the widget to use our profile and have autofill for link text + // enabled and try again. + $this->container->get('entity_display.repository')->getFormDisplay('node', 'page', 'default') ->setComponent('field_test_link', [ 'type' => 'linkit', 'settings' => [ 'linkit_profile' => $this->linkitProfile->id(), + 'linkit_auto_link_text' => TRUE, ], ]) ->save(); @@ -235,6 +244,24 @@ public function testLinkFieldWidgetAndFormatter() { $href_value = $link_element->getAttribute('href'); $this->assertContains('https://google.com#foobar', $href_value); + // Test that it is possible to add just the anchor. + $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('#foobar'); + $session->getDriver()->keyDown($uri_input->getXpath(), ' '); + $assert_session->waitOnAutocomplete(); + $title_input = $assert_session->elementExists('css', 'input[name="field_test_link[0][title]"]', $widget_wrapper); + $title_input->setValue('Just a fragment'); + $page->fillField('title[0][value]', 'Host test node 3.5'); + $page->pressButton('Save'); + $assert_session->pageTextContains('Host test node 3.5 has been created'); + + $field_wrapper = $assert_session->elementExists('css', '.field--type-link.field--name-field-test-link'); + $link_element = $assert_session->elementExists('css', 'a', $field_wrapper); + $href_value = $link_element->getAttribute('href'); + $this->assertEquals('#foobar', $href_value); + // Test emails. $this->drupalGet('node/add/page'); @@ -329,6 +356,32 @@ public function testLinkFieldWidgetAndFormatter() { $this->assertEquals('Front page', $link_element->getText()); $href_value = $link_element->getAttribute('href'); $this->assertContains('/', $href_value); + + // Test invalid input. + foreach (['foo:0123456', ':', '123:bar'] as $key => $invalid_string) { + $this->drupalGet('node/add/page'); + $page->fillField('title[0][value]', 'Invalid string node ' . $key); + $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($invalid_string); + $assert_session->assertWaitOnAjaxRequest(); + + $page->pressButton('Save'); + $assert_session->pageTextContains("$invalid_string' is invalid."); + } + + // Test valid ones. + foreach (['tel:0123456', 'irc:irc.freenode.net'] as $key => $invalid_string) { + $this->drupalGet('node/add/page'); + $page->fillField('title[0][value]', 'Valid string node ' . $key); + $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($invalid_string); + $assert_session->assertWaitOnAjaxRequest(); + + $page->pressButton('Save'); + $assert_session->pageTextContains("page Valid string node $key has been created"); + } } }