mmikkel / Retcon-Craft

A collection of powerful Twig filters for modifying HTML
MIT License
79 stars 9 forks source link

Retcon plugin for Craft CMS

Retcon is a tiny Craft CMS plugin adding a series of powerful Twig filters for modifying HTML content. Here are some of the things Retcon can do:

...and much more!

Requirements

This plugin requires Craft CMS 5.0+

How does it work?

Retcon uses PHP's native DOMDocument class to parse and modify HTML. Additionally, Masterminds' HTML5 library is used for HTML5 support, and Symfony's DomCrawler and CssSelector components are used to enable the powerful jQuery-like selector syntax.

Basic usage

Retcon exposes a series of different methods for modifying HTML. Most methods take a selector parameter (i.e. the selector(s) for the elements you want to modify, e.g. 'img', 'p' or 'div.foobar'), and some take additional parameters for further configuration.

Note that it doesn't matter if your HTML is from a WYSIWYG field (Redactor or CK Editor) or just a regular ol' string. If it's HTML, Retcon will eat it.

Twig filters

All of Retcon's methods are exposed as Twig filters, which is how Retcon is primarily designed to be used. For example, if you wanted to add a classname 'image' to all images in a Redactor field called body, here's how that'd look:

{{ entry.body | retconAttr('img', { class: 'image' }) }}

Note that for the Twig filters, the prefix retcon is added to the method name – i.e. the attr method becomes the retconAttr filter, the transform method becomes the retconTransform filter, etc.

Filter tag pair

For use cases where your HTML is not in a field or variable, the apply tag pair syntax works nicely – the following example adds rel="noopener noreferrer" to all <a> tags with target="_blank":

{% apply retconAttr('a[target="_blank"]:not([rel])', { rel: 'noopener noreferrer' }) %}
    {# A whole bunch of HTML in here #}
    ....
{% endapply %}

Catch-all filter

Being Twig filters, chaining multiple Retcon methods will of course work:

{{ entry.body | retconChange('h1,h2,h4,h5,h6', 'h3') | retconAttr('h3', { class: 'heading') }}

Another option is to use the "catch-all" filter retcon, which takes a single array containing the names of the methods you want to run in sequence, and their parameters:

{{ entry.body | retcon([
    ['change', 'h1,h2,h4,h5,h6', 'h3'],
    ['attr', 'h3', { class: 'heading' }]
]) }}

PHP

If you want to use Retcon in a Craft plugin or module, all methods are also available through the mmikkel/retcon/Retcon::getInstance()->retcon service (note that unlike the Twig filters, the retcon prefix is missing from the service method names – in other words, retconAttr is just attr()):

use mmikkel\retcon\Retcon;
echo Retcon::getInstance()->retcon->attr($entry->body, ['class' => 'image']);

For an actual use case example; here's how the rel="noopener noreferrer" example could look in a module (basically, the below code would add rel="noopener noreferrer" automatically to all <a target="_blank"> tags in your templates (unless they've already got a rel attribute set, of course):

use mmikkel\retcon;

public function init() {

    Event::on(
        View::class,
        View::EVENT_AFTER_RENDER_TEMPLATE,
        function (TemplateEvent $event) {
            if (!Craft::$app->getRequest()->getIsSiteRequest()) {
                return;
            }

            if ($event->output && Craft::$app->getPlugins()->getPlugin('retcon')) {
                $event->output =
                    Retcon::getInstance()->retcon->attr(
                        $event->output,
                        'a[target="_blank"]:not([rel])', [
                            'rel' => 'noopener noreferrer',
                        ]);
            }

        }
    );
}

Selectors

A "selector" in Retcon is the same thing as a selector in CSS; i.e. something like 'img', '.foo' or h3 + p.

Retcon's selector support is close to full parity with CSS, but not every selector will work. See the CssSelector docs for further details.

Multiple selectors

Multiple selectors can be defined as a comma-separated string (i.e. 'p, span') or as an array (i.e. ['p', 'span']).

Selecting top level nodes only

New (Retcon 3.1.0+ only):

If you need to limit your selection to top-level nodes only, the 'body' element selector can be used in conjunction with the child combinator ('>'). As an example:

{# Make all top level <p> tags red #}
{% apply retconAttr('body > p', { style: 'color: red;' }) %} 
    <p>I wanna be red</p>
    <div>
        <p>I wanna be left alone</p>
    </div>
{% endapply %}

Result:

<p style="color: red;">I wanna be red</p>
<div>
    <p>I wanna be left alone</p>
</div>

The body element selector is supported for all filters.

Methods

transform Apply a named or inline image transform to all images. If installed, Retcon uses Imager to apply the transform. New: Retcon also supports Imager's successor, Imager X.

srcset Apply an array of named or inline image transform to all images, for simple srcset support. If installed, Retcon uses Imager to apply the transforms.

lazy Replaces the src attribute of image tags with a transparent, base64 encoded SVG (retaining the original image's aspect ratio); putting the original src URL in a data-attribute

dimensions Adds width and height attributes to image nodes, if they are missing (and the image referenced in the image nodes' src attribute is a local image file). NEW

autoAlt Adds Asset title or filename as alternative text for images missing alt tags

attr Adds, replaces, appends to or removes a set of attributes and attribute values – e.g. class. Can be used to remove inline styles.

renameAttr Renames existing attributes for matching selectors, retaining the attribute values

wrap Wraps stuff in other stuff (e.g. put all <span> tags in <p> tags)

unwrap Removes parent node for matching elements; retaining their content

remove Removes all elements matching the given selector(s)

removeEmpty Removes all empty elements. NEW

only Removes everything except the elements matching the given selector(s)

change Changes tag type for all elements matching the given selector(s). Can also be used to remove tags completely, but retaining their content.

inject Inject strings or HTML

Disclaimer & support

Retcon is provided free of charge. The author is not responsible for data loss or any other problems resulting from the use of this plugin. Please see the Wiki page for documentation and examples. and report any bugs, feature requests or other issues here. As Retcon is a hobby project, no promises are made regarding response time, feature implementations or bug amendments.