From c1f160ce691b7eb82eae6d4a5c1b7b76da5f8737 Mon Sep 17 00:00:00 2001 From: orakili Date: Wed, 27 Sep 2023 01:21:54 +0000 Subject: [PATCH] fix: add target and rel attributes to external links to open in new tab for links inside fields using the markdown_editor (ckeditor 5) format Refs: RW-812 --- config/filter.format.markdown_editor.yml | 89 ++++++++++- .../src/Plugin/Filter/ExternalLinkFilter.php | 146 ++++++++++++++++++ 2 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 html/modules/custom/reliefweb_utility/src/Plugin/Filter/ExternalLinkFilter.php diff --git a/config/filter.format.markdown_editor.yml b/config/filter.format.markdown_editor.yml index beb80d89b..16e6c1add 100644 --- a/config/filter.format.markdown_editor.yml +++ b/config/filter.format.markdown_editor.yml @@ -3,7 +3,10 @@ langcode: en status: true dependencies: module: + - editor + - media - reliefweb_fields + - reliefweb_guidelines - reliefweb_utility name: 'Markdown with Editor' format: markdown_editor @@ -13,7 +16,7 @@ filters: id: filter_html provider: filter status: true - weight: -10 + weight: -49 settings: allowed_html: '

    1. ' filter_html_help: false @@ -22,24 +25,100 @@ filters: id: filter_htmlcorrector provider: filter status: true - weight: 10 + weight: -47 settings: { } filter_markdown: id: filter_markdown provider: reliefweb_utility status: true - weight: -20 + weight: -50 settings: { } reliefweb_token_filter: id: reliefweb_token_filter provider: reliefweb_utility status: false - weight: 0 + weight: -37 settings: replace_empty: '0' reliefweb_formatted_text: id: reliefweb_formatted_text provider: reliefweb_fields status: true - weight: 0 + weight: -48 + settings: { } + reliefweb_external_link_filter: + id: reliefweb_external_link_filter + provider: reliefweb_utility + status: true + weight: -46 + settings: { } + editor_file_reference: + id: editor_file_reference + provider: editor + status: false + weight: -44 + settings: { } + filter_html_escape: + id: filter_html_escape + provider: filter + status: false + weight: -45 + settings: { } + filter_url: + id: filter_url + provider: filter + status: false + weight: -40 + settings: + filter_url_length: 72 + filter_html_image_secure: + id: filter_html_image_secure + provider: filter + status: false + weight: -36 + settings: { } + filter_image_lazy_load: + id: filter_image_lazy_load + provider: filter + status: false + weight: -35 + settings: { } + filter_caption: + id: filter_caption + provider: filter + status: false + weight: -41 + settings: { } + filter_autop: + id: filter_autop + provider: filter + status: false + weight: -42 + settings: { } + filter_align: + id: filter_align + provider: filter + status: false + weight: -43 + settings: { } + media_embed: + id: media_embed + provider: media + status: false + weight: -34 + settings: + default_view_mode: default + allowed_view_modes: { } + allowed_media_types: { } + filter_guideline_link: + id: filter_guideline_link + provider: reliefweb_guidelines + status: false + weight: -39 + settings: { } + filter_iframe: + id: filter_iframe + provider: reliefweb_utility + status: false + weight: -38 settings: { } diff --git a/html/modules/custom/reliefweb_utility/src/Plugin/Filter/ExternalLinkFilter.php b/html/modules/custom/reliefweb_utility/src/Plugin/Filter/ExternalLinkFilter.php new file mode 100644 index 000000000..4dcd1bdaf --- /dev/null +++ b/html/modules/custom/reliefweb_utility/src/Plugin/Filter/ExternalLinkFilter.php @@ -0,0 +1,146 @@ +requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('request_stack') + ); + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode) { + if (is_string($text) || $text instanceof MarkupInterface) { + $html = trim($text); + if ($html !== '') { + // Adding this meta tag is necessary to tell \DOMDocument we are dealing + // with UTF-8 encoded html. + $flags = LIBXML_NONET | LIBXML_NOBLANKS | LIBXML_NOERROR | LIBXML_NOWARNING; + $meta = ''; + $prefix = '' . $meta . ''; + $suffix = ''; + $dom = new \DOMDocument(); + $dom->loadHTML($prefix . $text . $suffix, $flags); + + // Process the links. + $links = $dom->getElementsByTagName('a'); + foreach ($links as $link) { + $this->handleLink($link); + } + + // Get the modified html. + $html = $dom->saveHTML(); + + // Search for the body tag and return its content. + $start = mb_strpos($html, ''); + $end = mb_strrpos($html, ''); + if ($start !== FALSE && $end !== FALSE) { + $start += 6; + $text = trim(mb_substr($html, $start, $end - $start)); + } + } + } + return new FilterProcessResult($text); + } + + /** + * Check if a URL is an ReliefWeb URL. + * + * @param string $url + * URL to check. + * + * @return bool + * TRUE if the URL is internal. + */ + protected function isInternalUrl($url) { + if (empty($url)) { + return TRUE; + } + + if (!isset($this->internalHostPattern)) { + $internal_hosts = [ + preg_quote($this->requestStack->getCurrentRequest()->getHost()), + preg_quote('reliefweb.int'), + ]; + + $this->internalHostPattern = '#^https?://(' . implode('|', $internal_hosts) . ')(/|$)#'; + } + + return preg_match('#^https?://#', $url) !== 1 || + preg_match($this->internalHostPattern, $url) === 1; + } + + /** + * Add the target and rel attributes to external links to open in a new tab. + * + * @param \DOMNode $node + * Link node. + */ + protected function handleLink(\DOMNode $node) { + $url = $node->getAttribute('href'); + + if (!$this->isInternalUrl($url)) { + $node->setAttribute('target', '_blank'); + $node->setAttribute('rel', 'noreferrer noopener'); + } + } + +}