diff --git a/js/linkit.autocomplete.js b/js/linkit.autocomplete.js
index 558278d..4ea7fdb 100644
--- a/js/linkit.autocomplete.js
+++ b/js/linkit.autocomplete.js
@@ -97,6 +97,44 @@
* jQuery collection of the ul element.
*/
function renderItem(ul, item) {
+ var getUrl = window.location;
+ var baseUrl = getUrl .protocol + "//" + getUrl.host;
+ 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) {
+ $(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");
+ });
+ $(dialogForm).find('input.form-linkit-autocomplete').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'));
diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php
index 787c409..f4619f9 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\Core\Path\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\Core\Path\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/Kernel/Matchers/NodeMatcherTest.php b/tests/src/Kernel/Matchers/NodeMatcherTest.php
index 0ab5550..9b0ed6e 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,13 @@ class NodeMatcherTest extends LinkitKernelTestBase {
*
* @var array
*/
- public static $modules = ['field', 'node', 'content_moderation', 'workflows'];
+ public static $modules = [
+ 'field',
+ 'node',
+ 'content_moderation',
+ 'workflows',
+ 'language',
+ ];
/**
* The matcher manager.
@@ -35,7 +42,7 @@ class NodeMatcherTest extends LinkitKernelTestBase {
$this->installEntitySchema('node');
$this->installSchema('node', ['node_access']);
- $this->installConfig(['field', 'node']);
+ $this->installConfig(['field', 'node', 'language']);
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
@@ -180,4 +187,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->assertContains('/' . $langcode . '/', $translated_url);
+ $suggestions = $plugin->execute($translated_url);
+ $this->assertEquals(1, count($suggestions->getSuggestions()));
+ }
+
}