diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1437726345..ed7dc68e88 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -28,6 +28,7 @@ Changelog * Added name attributes to all built-in page action menu items (LB (Ben Johnston)) * Added validation on the filter string to the Jinja2 image template tag (Jonny Scholes) * Changed the pages reodering UI toggle to make it easier to find (Katie Locke, Thibaud Colas) + * Added support for rich text link rewrite handlers for `external` and `email` links (Md Arifin Ibne Matin) * Fix: Set `SERVER_PORT` to 443 in `Page.dummy_request()` for HTTPS sites (Sergey Fedoseev) * Fix: Include port number in `Host` header of `Page.dummy_request()` (Sergey Fedoseev) * Fix: Validation error messages in `InlinePanel` no longer count towards `max_num` when disabling the 'add' button (Todd Dembrey, Thibaud Colas) diff --git a/docs/advanced_topics/customisation/rich_text_internals.rst b/docs/advanced_topics/customisation/rich_text_internals.rst index 75529afe90..318f83720f 100644 --- a/docs/advanced_topics/customisation/rich_text_internals.rst +++ b/docs/advanced_topics/customisation/rich_text_internals.rst @@ -97,6 +97,22 @@ The ``register_link_type`` method allows you to define a function to be called w def register_report_link(features): features.register_link_type('report', report_link_handler) +It is also possible to define link rewrite handler for Wagtail’s built-in ``external`` and ``email`` links, even though they do not have a predefined ``linktype``. For example, if you want external links to have a ``rel="nofollow"`` attribute for SEO purposes: + +.. code-block:: python + + from django.utils.html import escape + from wagtail.core import hooks + + def external_link_handler(attrs): + href = attrs["href"] + return '' % escape(href) + + @hooks.register('register_rich_text_features') + def register_external_link(features): + features.register_link_type('external', external_link_handler) + +Similarly you can use ``email`` linktype to add a custom rewrite handler for email links (e.g. to obfuscate emails in rich text). Embed rewrite handlers ---------------------- diff --git a/docs/releases/2.5.rst b/docs/releases/2.5.rst index 4a8b9a8bb6..cb20a5f6b1 100644 --- a/docs/releases/2.5.rst +++ b/docs/releases/2.5.rst @@ -54,6 +54,7 @@ Other features * Added name attributes to all built-in page action menu items (LB (Ben Johnston)) * Added validation on the filter string to the Jinja2 image template tag (Jonny Scholes) * Changed the pages reodering UI toggle to make it easier to find (Katie Locke, Thibaud Colas) + * Added support for rich text link rewrite handlers for ``external`` and ``email`` links (Md Arifin Ibne Matin) Bug fixes diff --git a/wagtail/core/rich_text/rewriters.py b/wagtail/core/rich_text/rewriters.py index b690810027..904eaad661 100644 --- a/wagtail/core/rich_text/rewriters.py +++ b/wagtail/core/rich_text/rewriters.py @@ -56,12 +56,28 @@ class LinkRewriter: try: link_type = attrs['linktype'] except KeyError: - # return ordinary links without a linktype unchanged - return match.group(0) + link_type = None + href = attrs.get('href', None) + if href: + # From href attribute we try to detect only the linktypes that we + # currently support (`external` & `email`, `page` has a default handler) + # from the link chooser. + if href.startswith(('http:', 'https:')): + link_type = 'external' + elif href.startswith('mailto:'): + link_type = 'email' + + if not link_type: + # otherwise return ordinary links without a linktype unchanged + return match.group(0) try: rule = self.link_rules[link_type] except KeyError: + if link_type in ['email', 'external']: + # If no rule is registered for supported types + # return ordinary links without a linktype unchanged + return match.group(0) # unrecognised link type return '' diff --git a/wagtail/core/tests/test_rich_text.py b/wagtail/core/tests/test_rich_text.py index 2546ac2135..d6e6254e37 100644 --- a/wagtail/core/tests/test_rich_text.py +++ b/wagtail/core/tests/test_rich_text.py @@ -7,7 +7,7 @@ from wagtail.core.models import Page from wagtail.core.rich_text import RichText, expand_db_html from wagtail.core.rich_text.feature_registry import FeatureRegistry from wagtail.core.rich_text.pages import PageLinkHandler, page_linktype_handler -from wagtail.core.rich_text.rewriters import extract_attrs +from wagtail.core.rich_text.rewriters import LinkRewriter, extract_attrs class TestPageLinkHandler(TestCase): @@ -117,3 +117,63 @@ class TestFeatureRegistry(TestCase): self.assertIsNone( features.get_editor_plugin('hallo', 'made_up_feature') ) + + +class TestLinkRewriterTagReplacing(TestCase): + def test_should_follow_default_behaviour(self): + # we always have default `page` rules registered. + rules = { + 'page': lambda attrs: ''.format(attrs['id']) + } + rewriter = LinkRewriter(rules) + + page_type_link = rewriter('') + self.assertEqual(page_type_link, '') + + # but it should also be able to handle other supported + # link types (email, external) even if no rules is provided + external_type_link = rewriter('') + self.assertEqual(external_type_link, '') + email_type_link = rewriter('') + self.assertEqual(email_type_link, '') + + # As well as link which don't have any linktypes + link_without_linktype = rewriter('') + self.assertEqual(link_without_linktype, '') + + # But should not handle if a custom linktype is mentioned but no + # associate rules are registered. + link_with_custom_linktype = rewriter('') + self.assertNotEqual(link_with_custom_linktype, '') + self.assertEqual(link_with_custom_linktype, '') + + + def test_supported_type_should_follow_given_rules(self): + # we always have `page` rules by default + rules = { + 'page': lambda attrs: ''.format(attrs['id']), + 'external': lambda attrs: ''.format(attrs['href']), + 'email': lambda attrs: ''.format(attrs['href']), + 'custom': lambda attrs: ''.format(attrs['href']), + } + rewriter = LinkRewriter(rules) + + page_type_link = rewriter('') + self.assertEqual(page_type_link, '') + + # It should call appropriate rule supported linktypes (external or email) + # based on the href value + external_type_link = rewriter('') + self.assertEqual(external_type_link, '') + external_type_link_http = rewriter('') + self.assertEqual(external_type_link_http, '') + email_type_link = rewriter('') + self.assertEqual(email_type_link, '') + + # But not the unsupported ones. + link_with_no_linktype = rewriter('') + self.assertEqual(link_with_no_linktype, '') + + # Also call the rule if a custom linktype is mentioned. + link_with_custom_linktype = rewriter('') + self.assertEqual(link_with_custom_linktype, '')