From b9a548f42483f639bba1ac2c35250bc2c56317b5 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Fri, 5 Jan 2018 16:15:26 +0000 Subject: [PATCH] Update documentation for rich text element whitelisting --- .../customisation/page_editing_interface.rst | 56 +++++++++++++++---- docs/reference/hooks.rst | 10 ++++ docs/releases/2.0.rst | 36 ++++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/docs/advanced_topics/customisation/page_editing_interface.rst b/docs/advanced_topics/customisation/page_editing_interface.rst index 0b505dbcf5..2001b01f6a 100644 --- a/docs/advanced_topics/customisation/page_editing_interface.rst +++ b/docs/advanced_topics/customisation/page_editing_interface.rst @@ -74,7 +74,7 @@ This can be achieved by passing a ``features`` keyword argument to ``RichTextFie body = RichTextField(features=['h2', 'h3', 'bold', 'italic', 'link']) -The recognised feature identifiers are as follows (note that add-on modules may add to this list): +The feature identifiers provided on a default Wagtail installation are as follows: * ``h1``, ``h2``, ``h3``, ``h4``, ``h5``, ``h6`` - heading elements * ``bold``, ``italic`` - bold / italic text @@ -85,14 +85,27 @@ The recognised feature identifiers are as follows (note that add-on modules may * ``image`` - embedded images * ``embed`` - embedded media (see :ref:`embedded_content`) + +Adding new features to this list is generally a two step process: + + * Create a plugin that extends the editor with a new toolbar button for adding a particular HTML element + * Add that HTML element to the whitelist of elements that are permitted in rich text output + +Both of these steps are performed through the ``register_rich_text_features`` hook (see :ref:`admin_hooks`). The hook function is triggered on startup, and receives a *feature registry* object as its argument; this object keeps track of the behaviours associated with each feature identifier. + +This process for adding new features is described in the following sections. + + .. _extending_wysiwyg: Extending the WYSIWYG Editor (``hallo.js``) -------------------------------------------- ++++++++++++++++++++++++++++++++++++++++++++ Wagtail's rich text editor is built on ``hallo.js``, and its functionality can be extended through plugins. For information on developing custom ``hallo.js`` plugins, see the project's page: https://github.com/bergie/hallo -Once the plugin has been created, it should be registered as a rich text feature using the ``register_rich_text_features`` hook. For example, a plugin ``halloblockquote``, implemented in ``myapp/js/hallo-blockquote.js``, that adds support for the ``
`` tag, would be registered under the feature name ``blockquote`` as follows: +Once the plugin has been created, it should be registered through the feature registry's ``register_editor_plugin(editor, feature_name, plugin)`` method. For a ``hallo.js`` plugin, the ``editor`` parameter should always be ``'hallo'``. + +A plugin ``halloblockquote``, implemented in ``myapp/js/hallo-blockquote.js``, that adds support for the ``
`` tag, would be registered under the feature name ``block-quote`` as follows: .. code-block:: python @@ -102,17 +115,13 @@ Once the plugin has been created, it should be registered as a rich text feature @hooks.register('register_rich_text_features') def register_embed_feature(features): features.register_editor_plugin( - 'hallo', 'blockquote', + 'hallo', 'block-quote', HalloPlugin( name='halloblockquote', js=['myapp/js/hallo-blockquote.js'], ) ) -.. note:: - - When extending the rich text editor to support a new HTML element, it will also be necessary to update the HTML whitelisting rules, via the :ref:`construct_whitelister_element_rules` hook. - The constructor for ``HalloPlugin`` accepts the following keyword arguments: * ``name`` - the plugin name as defined in the Javascript code. ``hallo.js`` plugin names are prefixed with the ``"IKS."`` namespace, but the name passed here should be without the prefix. @@ -130,10 +139,37 @@ To have a feature active by default (i.e. on ``RichTextFields`` that do not defi @hooks.register('register_rich_text_features') def register_blockquote_feature(features): features.register_editor_plugin( - 'hallo', 'blockquote', + 'hallo', 'block-quote', # ... ) - features.default_features.append('blockquote') + features.default_features.append('block-quote') + + +.. _whitelisting_rich_text_elements: + +Whitelisting rich text elements ++++++++++++++++++++++++++++++++ + +After extending the editor to support a new HTML element, you'll need to add it to the whitelist of permitted elements - Wagtail's standard behaviour is to strip out unrecognised elements, to prevent editors from inserting styles and scripts (either deliberately, or inadvertently through copy-and-paste) that the developer didn't account for. + +Elements can be added to the whitelist through the feature registry's ``register_converter_rule(converter, feature_name, ruleset)`` method. When the ``hallo.js`` editor is in use, the ``converter`` parameter should always be ``'editorhtml'``. + +The following code will add the ``
`` element to the whitelist whenever the ``block-quote`` feature is active: + +.. code-block:: python + + from wagtail.admin.rich_text.converters.editor_html import WhitelistRule + from wagtail.core.whitelist import allow_without_attributes + + @hooks.register('register_rich_text_features') + def register_blockquote_feature(features): + features.register_converter_rule('editorhtml', 'block-quote', [ + WhitelistRule('blockquote', allow_without_attributes), + ]) + +``WhitelistRule`` is passed the element name, and a callable which will perform some kind of manipulation of the element whenever it is encountered. This callable receives the element as a `BeautifulSoup `_ Tag object. + +The ``wagtail.core.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute). .. _rich_text_image_formats: diff --git a/docs/reference/hooks.rst b/docs/reference/hooks.rst index dbaa5dcc9e..9eb9b750f9 100644 --- a/docs/reference/hooks.rst +++ b/docs/reference/hooks.rst @@ -266,11 +266,21 @@ Editor interface Hooks for customising the editing interface for pages and snippets. +.. _register_rich_text_features: + +``register_rich_text_features`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Rich text fields in Wagtail work with a list of 'feature' identifiers that determine which editing controls are available in the editor, and which elements are allowed in the output; for example, a rich text field defined as ``RichTextField(features=['h2', 'h3', 'bold', 'italic', 'link'])`` would allow headings, bold / italic formatting and links, but not (for example) bullet lists or images. The ``register_rich_text_features`` hook allows new feature identifiers to be defined - see :ref:`rich_text_features` for details. + + .. _construct_whitelister_element_rules: ``construct_whitelister_element_rules`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Deprecated.** This hook will be removed in Wagtail 2.2; please use :ref:`rich text features ` to define whitelist rules instead. + Customise the rules that define which HTML elements are allowed in rich text areas. By default only a limited set of HTML elements and attributes are whitelisted - all others are stripped out. The callables passed into this hook must return a dict, which maps element names to handler functions that will perform some kind of manipulation of the element. These handler functions receive the element as a `BeautifulSoup `_ Tag object. The ``wagtail.core.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute). diff --git a/docs/releases/2.0.rst b/docs/releases/2.0.rst index b4aa1f8d3d..a2d8410d8c 100644 --- a/docs/releases/2.0.rst +++ b/docs/releases/2.0.rst @@ -167,6 +167,42 @@ This repository has been created to provide a place for the community to collabo legacy versions of the API until everyone has migrated to an officially supported version. +``construct_whitelister_element_rules`` hook is deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``construct_whitelister_element_rules`` hook, used to specify additional HTML elements to be permitted in rich text, is deprecated. The recommended way of whitelisting elements is now to use rich text features - see :ref:`whitelisting_rich_text_elements`. For example, a whitelist rule that was previously defined as: + + .. code-block:: python + + from wagtail.core import hooks + from wagtail.core.whitelist import allow_without_attributes + + @hooks.register('construct_whitelister_element_rules') + def whitelist_blockquote(): + return { + 'blockquote': allow_without_attributes, + } + +can be rewritten as: + + .. code-block:: python + + from wagtail.admin.rich_text.converters.editor_html import WhitelistRule + from wagtail.core import hooks + from wagtail.core.whitelist import allow_without_attributes + + @hooks.register('register_rich_text_features') + def blockquote_feature(features): + + # register a feature 'blockquote' which whitelists the
element + features.register_converter_rule('editorhtml', 'blockquote', [ + WhitelistRule('blockquote', allow_without_attributes), + ]) + + # add 'blockquote' to the default feature set + features.default_features.append('blockquote') + + ``wagtail.images.views.serve.generate_signature`` now returns a string ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~