config/schema/linkit.schema.yml | 14 ++++++ src/Plugin/Filter/LinkitFilter.php | 88 +++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/config/schema/linkit.schema.yml b/config/schema/linkit.schema.yml index 40de218..bf8175a 100644 --- a/config/schema/linkit.schema.yml +++ b/config/schema/linkit.schema.yml @@ -121,3 +121,17 @@ linkit.linkit_profile.*.third_party.imce: type: boolean scheme: type: string + +filter_settings.linkit: + type: filter + label: 'Filter HTML' + mapping: + allowed_html: + type: string + label: 'Allowed HTML' + filter_html_help: + type: boolean + label: 'HTML help' + filter_html_nofollow: + type: boolean + label: 'HTML nofollow' diff --git a/src/Plugin/Filter/LinkitFilter.php b/src/Plugin/Filter/LinkitFilter.php index 454fa92..836b1b4 100644 --- a/src/Plugin/Filter/LinkitFilter.php +++ b/src/Plugin/Filter/LinkitFilter.php @@ -8,27 +8,67 @@ namespace Drupal\linkit\Plugin\Filter; use Drupal\Component\Utility\Html; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Url; use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; +use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a linkit url filter. + * Provides a Linkit filter. + * + * Note this must run before any Xss::filter() calls, because that strips + * disallowed protocols. That effectively means this must run before the + * \Drupal\filter\Plugin\Filter\FilterHtml filter. Hence the very low weight. * * @Filter( - * id = "linkit_filter", + * id = "linkit", * title = @Translation("Linkit filter"), - * description = @Translation("Linkit filter description"), - * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE + * description = @Translation("Updates content links inserted by Linkit to point to the current URL alias, and have the current title."), + * settings = { + * "title" = TRUE, + * }, + * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE, + * weight = -15, * ) */ -class LinkitFilter extends FilterBase { +class LinkitFilter extends FilterBase implements ContainerFactoryPluginInterface { + + /** + * Constructs a LinkitFilter object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository + * The entity repository service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->entityTypeManager = $entity_type_manager; + $this->entityRepository = $entity_repository; + } /** * {@inheritdoc} */ - public function calculateDependencies() { - return array('module' => array('linkit')); + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('entity.repository') + ); } /** @@ -41,9 +81,30 @@ class LinkitFilter extends FilterBase { $xpath = new \DOMXPath($dom); /** @var \DOMElement $node */ - foreach ($xpath->query('//a[contains(@href, "entity:")]') as $node) { + foreach ($xpath->query('//a[starts-with(@href, "entity:")]') as $node) { $href = $node->getAttribute('href'); - $node->setAttribute('href', Url::fromUri($href)->toString()); + + // Parse the entity: URI into an entity type ID and entity ID. + $uri_parts = parse_url($href); + list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2); + + // Load the appropriate translation of the linked entity. + $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id); + $entity = $this->entityRepository->getTranslationFromContext($entity, $langcode); + + // Set the appropriate + $url = $entity->toUrl()->toString(TRUE); + $node->setAttribute('href', $url->getGeneratedUrl()); + if ($this->settings['title']) { + $node->setAttribute('title', $entity->label()); + } + + // The processed text now depends on: + $result + // - the generated URL (which has undergone path & route processing) + ->addCacheableDependency($url) + // - the linked entity (whose URL and title may change) + ->addCacheableDependency($entity); } $result->setProcessedText(Html::serialize($dom)); @@ -54,8 +115,13 @@ class LinkitFilter extends FilterBase { /** * {@inheritdoc} */ - public function tips($long = FALSE) { - // @TODO: Do we need tips? + public function settingsForm(array $form, FormStateInterface $form_state) { + $form['title'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Automatically set the title attribute to that of the (translated) referenced content'), + '#default_value' => $this->settings['title'], + ]; + return $form; } }