diff --git a/js/linkit.autocomplete.js b/js/linkit.autocomplete.js
index 558278d..302a5d3 100644
--- a/js/linkit.autocomplete.js
+++ b/js/linkit.autocomplete.js
@@ -3,7 +3,7 @@
* Linkit Autocomplete based on jQuery UI.
*/
-(function ($, Drupal, _) {
+(function ($, Drupal, drupalSettings) {
'use strict';
@@ -37,6 +37,11 @@
// Get the desired term and construct the autocomplete URL for it.
var term = request.term;
+ var baseUrl = drupalSettings.base_url;
+ var term = term.replace(baseUrl,'');
+
+ // Make sure that we aren't looking for absolute url to self.
+ request.term = term;
// Check if the term is already cached.
if (autocomplete.cache[elementId].hasOwnProperty(term)) {
@@ -97,6 +102,43 @@
* jQuery collection of the ul element.
*/
function renderItem(ul, item) {
+ var baseUrl = drupalSettings.base_url;
+ var dialogForm = $('#editor-link-dialog-form form');
+ var inputEl = $(dialogForm).find('input.form-linkit-autocomplete');
+ var userInputVal = $(inputEl).val();
+ if (typeof userInputVal != 'undefined') {
+ var newInput = userInputVal.replace(baseUrl,'');
+ if (userInputVal.length > newInput.length) {
+ var msg = Drupal.t('URL provided was absolute internal, rather than use absolute links for content within this website you should instead select from one of the items in the dropdown list.');
+ $(dialogForm).find('div > label.form-item__label').text(msg).css('color', 'red');
+ $(dialogForm).closest('div.editor-link-dialog').find('button.form-submit').attr("disabled","disabled");
+ if (newInput.indexOf('auHash=') > 1 || newInput != userInputVal) {
+ $(inputEl).data('notabsolute', newInput);
+ $(inputEl).one("lostfocus", function() {
+ var notAbsolute = $(this).data('notabsolute');
+ $(this).val(notAbsolute);
+ var dialogForm = $('#editor-link-dialog-form form');
+ $(dialogForm).closest('div.editor-link-dialog').find('button.form-submit').attr("disabled","disabled");
+ });
+ $(inputEl).val(newInput);
+ }
+ $(dialogForm).closest('div.editor-link-dialog').find('ul.linkit-ui-autocomplete').each(function( index ) {
+ $(this).on('click.linkitresults' + index, function(e) {
+ // On click of the results, re-enable the button.
+ $('.editor-link-dialog button').each(function( index, el ) {
+ $(el).removeAttr("disabled");
+ $('#editor-link-dialog-form form label').first().text(Drupal.t('URL')).css('color', 'black');
+ });
+ });
+ });
+ }
+ else {
+ // Restore original message.
+ $(dialogForm).closest('div.editor-link-dialog').find('button.form-submit').removeAttr("disabled","disabled");
+ $('#editor-link-dialog-form form label').first().text(Drupal.t('URL')).css('color', 'black');
+ }
+ }
+
var $line = $('
').addClass('linkit-result-line');
var $wrapper = $('').addClass('linkit-result-line-wrapper');
$wrapper.append($('').html(item.label).addClass('linkit-result-line--title'));
@@ -212,4 +254,4 @@
}
};
-})(jQuery, Drupal, _);
+})(jQuery, Drupal, drupalSettings);
diff --git a/src/Controller/AutocompleteController.php b/src/Controller/AutocompleteController.php
index 0bcdeaf..722d5d1 100644
--- a/src/Controller/AutocompleteController.php
+++ b/src/Controller/AutocompleteController.php
@@ -76,7 +76,19 @@ class AutocompleteController implements ContainerInjectionInterface {
$this->linkitProfile = $this->linkitProfileStorage->load($linkit_profile_id);
$string = $request->query->get('q');
- $suggestionCollection = $this->suggestionManager->getSuggestions($this->linkitProfile, mb_strtolower($string));
+ // Get params can be case sensitive, we don't want to modify those.
+ // So only modify path for lower case not get params.
+ $posParams = strpos($string, '?');
+ if ($posParams > 0) {
+ $path = substr($string, 0, strpos($string, '?'));
+ $lowerPath = mb_strtolower($path);
+ $string = $lowerPath . substr($string, $posParams);
+ }
+ else {
+ // No get params.
+ $string = mb_strtolower($string);
+ }
+ $suggestionCollection = $this->suggestionManager->getSuggestions($this->linkitProfile, $string);
/*
* If there are no suggestions from the matcher plugins, we have to add a
diff --git a/src/Element/Linkit.php b/src/Element/Linkit.php
index 2bb7963..5da2718 100644
--- a/src/Element/Linkit.php
+++ b/src/Element/Linkit.php
@@ -69,6 +69,8 @@ class Linkit extends FormElement {
$metadata = BubbleableMetadata::createFromRenderArray($element);
if ($access->isAllowed()) {
$element['#attributes']['class'][] = 'form-linkit-autocomplete';
+ global $base_url; // Fully support multisite and any style base path.
+ $metadata->addAttachments(['drupalSettings' => ['base_url' => $base_url]]);
$metadata->addAttachments(['library' => ['linkit/linkit.autocomplete']]);
// Provide a data attribute for the JavaScript behavior to bind to.
$element['#attributes']['data-autocomplete-path'] = $url->getGeneratedUrl();
diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php
index e5a9413..c1d31d8 100644
--- a/src/Plugin/Linkit/Matcher/EntityMatcher.php
+++ b/src/Plugin/Linkit/Matcher/EntityMatcher.php
@@ -3,6 +3,8 @@
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
@@ -12,8 +14,10 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\path_alias\AliasManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
+use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\linkit\ConfigurableMatcherBase;
use Drupal\linkit\MatcherTokensTrait;
use Drupal\linkit\SubstitutionManagerInterface;
@@ -97,10 +101,24 @@ class EntityMatcher extends ConfigurableMatcherBase {
*/
protected $substitutionManager;
+ /**
+ * The alias manager.
+ *
+ * @var \Drupal\path_alias\AliasManagerInterface
+ */
+ protected $aliasManager;
+
+ /**
+ * The config factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
/**
* {@inheritdoc}
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager) {
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager, AliasManagerInterface $alias_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
if (empty($plugin_definition['target_entity'])) {
@@ -114,6 +132,8 @@ class EntityMatcher extends ConfigurableMatcherBase {
$this->currentUser = $current_user;
$this->targetType = $plugin_definition['target_entity'];
$this->substitutionManager = $substitution_manager;
+ $this->aliasManager = $alias_manager;
+ $this->configFactory = $config_factory;
}
/**
@@ -130,7 +150,9 @@ class EntityMatcher extends ConfigurableMatcherBase {
$container->get('entity.repository'),
$container->get('module_handler'),
$container->get('current_user'),
- $container->get('plugin.manager.linkit.substitution')
+ $container->get('plugin.manager.linkit.substitution'),
+ $container->get('path_alias.manager'),
+ $container->get('config.factory')
);
}
@@ -344,6 +366,12 @@ class EntityMatcher extends ConfigurableMatcherBase {
$entity = $this->entityRepository->getTranslationFromContext($entity);
$suggestion = $this->createSuggestion($entity);
+ if ($query = parse_url($string, PHP_URL_QUERY)) {
+ $suggestion->setPath($suggestion->getPath() . '?' . $query);
+ }
+ if ($fragment = parse_url($string, PHP_URL_FRAGMENT)) {
+ $suggestion->setPath($suggestion->getPath() . '#' . $fragment);
+ }
$suggestions->addSuggestion($suggestion);
}
@@ -516,12 +544,46 @@ class EntityMatcher extends ConfigurableMatcherBase {
* and a match is found, otherwise an empty array.
*/
protected function findEntityIdByUrl($user_input) {
+ $options = [];
+ if (UrlHelper::isExternal($user_input) && UrlHelper::isValid($user_input, TRUE)) {
+ if (UrlHelper::externalIsLocal($user_input, \Drupal::request()->getSchemeAndHttpHost())) {
+ // The link points to this domain. Make it relative so it can be
+ // matched in Url::fromUserInput().
+ $host = parse_url($user_input, PHP_URL_HOST);
+ $host_end = strpos($user_input, $host) + strlen($host);
+ $user_input = substr($user_input, $host_end);
+
+ if ($this->moduleHandler->moduleExists('language')) {
+ $config = $this->configFactory->get('language.negotiation')->get('url');
+ if ($config['source'] === LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
+ $language_manager = \Drupal::service('language_manager');
+ /** @var \Drupal\Core\Language\Language[] $languages */
+ $languages = $language_manager->getLanguages();
+ // Remove the leading slash for easier manipulation of the remaining
+ // args.
+ $path = urldecode(trim($user_input, '/'));
+ $path_args = explode('/', $path);
+ $prefix = array_shift($path_args);
+
+ // Search prefix within added languages.
+ foreach ($languages as $language) {
+ if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
+ $user_input = '/' . implode('/', $path_args);
+ $user_input = $this->aliasManager->getPathByAlias($user_input, $language->getId());
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
$result = [];
try {
- $params = Url::fromUserInput($user_input)->getRouteParameters();
- if (key($params) === $this->targetType) {
- $result = [end($params)];
+ $params = Url::fromUserInput($user_input, $options)->getRouteParameters();
+ if (!empty($params[$this->targetType])) {
+ $result = [$params[$this->targetType]];
}
}
catch (Exception $e) {
diff --git a/tests/src/Functional/LinkitBrowserTestBase.php b/tests/src/Functional/LinkitBrowserTestBase.php
index bc61b6c..30b7372 100644
--- a/tests/src/Functional/LinkitBrowserTestBase.php
+++ b/tests/src/Functional/LinkitBrowserTestBase.php
@@ -14,7 +14,7 @@ abstract class LinkitBrowserTestBase extends BrowserTestBase {
*
* @var array
*/
- public static $modules = ['linkit', 'linkit_test', 'block'];
+ public static $modules = ['linkit', 'linkit_test', 'block', 'path_alias'];
/**
* {@inheritdoc}
diff --git a/tests/src/FunctionalJavascript/LinkitDialogTest.php b/tests/src/FunctionalJavascript/LinkitDialogTest.php
index 26eb74e..f9b4bc2 100644
--- a/tests/src/FunctionalJavascript/LinkitDialogTest.php
+++ b/tests/src/FunctionalJavascript/LinkitDialogTest.php
@@ -29,6 +29,7 @@ class LinkitDialogTest extends WebDriverTestBase {
*/
public static $modules = [
'node',
+ 'path_alias',
'ckeditor',
'filter',
'linkit',
diff --git a/tests/src/FunctionalJavascript/LinkitFormatAdminTest.php b/tests/src/FunctionalJavascript/LinkitFormatAdminTest.php
index 7ab46ff..0d958cc 100644
--- a/tests/src/FunctionalJavascript/LinkitFormatAdminTest.php
+++ b/tests/src/FunctionalJavascript/LinkitFormatAdminTest.php
@@ -16,7 +16,7 @@ class LinkitFormatAdminTest extends WebDriverTestBase {
*
* @var array
*/
- public static $modules = ['editor', 'filter', 'linkit'];
+ public static $modules = ['editor', 'filter', 'linkit', 'path_alias'];
/**
* {@inheritdoc}
diff --git a/tests/src/Kernel/EntityMatcherDeriverTest.php b/tests/src/Kernel/EntityMatcherDeriverTest.php
index cf3832a..c7668dc 100644
--- a/tests/src/Kernel/EntityMatcherDeriverTest.php
+++ b/tests/src/Kernel/EntityMatcherDeriverTest.php
@@ -12,7 +12,7 @@ class EntityMatcherDeriverTest extends LinkitKernelTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = ['block', 'block_content', 'node', 'field'];
+ public static $modules = ['block', 'block_content', 'node', 'path_alias', 'field'];
/**
* The matcher manager.
diff --git a/tests/src/Kernel/LinkitKernelTestBase.php b/tests/src/Kernel/LinkitKernelTestBase.php
index d04a2bc..6ba9e2a 100644
--- a/tests/src/Kernel/LinkitKernelTestBase.php
+++ b/tests/src/Kernel/LinkitKernelTestBase.php
@@ -21,6 +21,7 @@ abstract class LinkitKernelTestBase extends KernelTestBase {
'user',
'filter',
'text',
+ 'path_alias',
'linkit',
'linkit_test',
];
diff --git a/tests/src/Kernel/Matchers/NodeMatcherTest.php b/tests/src/Kernel/Matchers/NodeMatcherTest.php
index f225eaf..a36d4e0 100644
--- a/tests/src/Kernel/Matchers/NodeMatcherTest.php
+++ b/tests/src/Kernel/Matchers/NodeMatcherTest.php
@@ -2,6 +2,7 @@
namespace Drupal\Tests\linkit\Kernel\Matchers;
+use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\linkit\Kernel\LinkitKernelTestBase;
@@ -18,7 +19,14 @@ class NodeMatcherTest extends LinkitKernelTestBase {
*
* @var array
*/
- public static $modules = ['field', 'node', 'content_moderation', 'workflows'];
+ public static $modules = [
+ 'field',
+ 'node',
+ 'path_alias',
+ 'content_moderation',
+ 'workflows',
+ 'language',
+ ];
/**
* The matcher manager.
@@ -35,7 +43,7 @@ class NodeMatcherTest extends LinkitKernelTestBase {
$this->installEntitySchema('node');
$this->installSchema('node', ['node_access']);
- $this->installConfig(['field', 'node']);
+ $this->installConfig(['field', 'node', 'language', 'path_alias']);
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
@@ -180,4 +188,49 @@ class NodeMatcherTest extends LinkitKernelTestBase {
}
}
+ /**
+ * Test node matches generated from an absolute URL input.
+ */
+ public function testNodeMatcherFromAbsoluteUrl() {
+ /** @var \Drupal\linkit\MatcherInterface $plugin */
+ $plugin = $this->manager->createInstance('entity:node');
+
+ /** @var \Drupal\node\NodeInterface[] $nodes */
+ $nodes = $this->container->get('entity_type.manager')->getStorage('node')->loadByProperties(['title' => 'Lorem Ipsum 1']);
+ $node = reset($nodes);
+
+ $suggestions = $plugin->execute($node->toUrl()->setAbsolute()->toString());
+ $this->assertEquals(1, count($suggestions->getSuggestions()));
+ }
+
+ /**
+ * Test node matches generated from an absolute URL input.
+ */
+ public function testNodeMatcherFromAbsoluteUrlWithLanguagePrefix() {
+ /** @var \Drupal\linkit\MatcherInterface $plugin */
+ $plugin = $this->manager->createInstance('entity:node');
+
+ $langcode = 'nl';
+ ConfigurableLanguage::createFromLangcode($langcode)->save();
+ \Drupal::configFactory()->getEditable('language.negotiation')
+ ->set('url.prefixes.nl', $langcode)
+ ->save();
+
+ // In order to reflect the changes for a multilingual site in the container
+ // we have to rebuild it.
+ \Drupal::service('kernel')->rebuildContainer();
+
+ /** @var \Drupal\node\NodeInterface[] $nodes */
+ $nodes = $this->container->get('entity_type.manager')->getStorage('node')->loadByProperties(['title' => 'Lorem Ipsum 1']);
+ $node = reset($nodes);
+ $translation = $node->addTranslation($langcode, $node->toArray());
+ $translation->save();
+
+ $translated_url = $translation->toUrl()->setAbsolute()->toString();
+ // Make sure the translated URL contains our prefix.
+ $this->assertStringContainsString('/' . $langcode . '/', (string) $translated_url);
+ $suggestions = $plugin->execute($translated_url);
+ $this->assertEquals(1, count($suggestions->getSuggestions()));
+ }
+
}