diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php index 33296be..d2cec37 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') ); } @@ -182,12 +204,12 @@ class EntityMatcher extends ConfigurableMatcherBase { */ public function defaultConfiguration() { return [ - 'metadata' => '', - 'bundles' => [], - 'group_by_bundle' => FALSE, - 'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION, - 'limit' => static::DEFAULT_LIMIT, - ] + parent::defaultConfiguration(); + 'metadata' => '', + 'bundles' => [], + 'group_by_bundle' => FALSE, + 'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION, + 'limit' => static::DEFAULT_LIMIT, + ] + parent::defaultConfiguration(); } /** @@ -491,18 +519,7 @@ class EntityMatcher extends ConfigurableMatcherBase { * The path for this entity. */ protected function buildPath(EntityInterface $entity) { - $path = $entity->toUrl('canonical', ['path_processing' => FALSE])->toString(); - // For media entities, check if standalone URLs are allowed. If not, then - // strip '/edit' from the end of the canonical URL returned - // by $entity->toUrl(). - if ($entity->getEntityTypeId() == 'media') { - $standalone_url = \Drupal::config('media.settings')->get('standalone_url'); - if ($standalone_url) { - // Strip "/edit". - $path = substr($path, 0, -5); - } - } - return $path; + return $entity->toUrl('canonical', ['path_processing' => FALSE])->toString(); } /** @@ -516,12 +533,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)) { + 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..e622bd0 100644 --- a/tests/src/Kernel/Matchers/NodeMatcherTest.php +++ b/tests/src/Kernel/Matchers/NodeMatcherTest.php @@ -180,4 +180,19 @@ 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())); + } + }