Question

I'm trying to allow admin users to edit email templates. These templates are stored in the DB as Twig ones. So the variables in them are set as {{ purchase.number }} and there are loops like

    {% if cart['shipping'] %}
        {% for line in cart['shipping'] %}
            <tr>
                <td colspan="7">Shipping ({{ line['text'] }})</td>
                <td>US${{ line['money'] }}</td>
            </tr>
        {% endfor %}
    {% endif %}

Below is one of the templates where I can reproduce this issue:

        <html>
    <body>
        <h3>Order #{{ purchase.number }} was cancelled</h3>
        <p>Order content:</p>
        <table>
            <tr>
                <th>Line</th>
                <th>Item #</th>
                <th>Product Name</th>
                <th>Shipping</th>
                <th>UOM</th>
                <th>Unit Price</th>
                <th>Quantity</th>
                <th>Subtotal</th>
            </tr>
            {% for line in cart['cart'] %}
                <tr>
                    <td>{{ line['LineNo'] }}</td>
                    <td>{{ line['ItemNo'] }}</td>
                    <td>{{ line['ProductName'] }}</td>
                    <td>{{ line['Shipping'] }}</td>
                    <td>{{ line['UOM'] }}</td>
                    <td>US${{ line['UnitPrice'] }}</td>
                    <td>{{ line['Quantity'] }}</td>
                    <td>US${{ line['Subtotal'] }}</td>
                </tr>
            {% endfor %}
            {% if cart['shipping'] %}
                {% for line in cart['shipping'] %}
                    <tr>
                        <td colspan="7">Shipping ({{ line['text'] }})</td>
                        <td>US${{ line['money'] }}</td>
                    </tr>
                {% endfor %}
            {% endif %}
            <tr>
                <td colspan="7"><b>Order Item Total:</b></td>
                <td>US${{ cart['total'] }}</td>
            </tr>
        </table>
    </body>
</html>

When I just open a page with CKEditor textarea with this template in it, I do no changes to the template and just click on "Source" button and here is how the above mentioned template looks after the click:

<h3>Order #{{ purchase.number }} was cancelled</h3>

<p>Order content:</p>
{% for line in cart[&#39;cart&#39;] %} {% endfor %} {% if cart[&#39;shipping&#39;] %} {% for line in cart[&#39;shipping&#39;] %} {% endfor %} {% endif %}

<table>
    <tbody>
        <tr>
            <th>Line</th>
            <th>Item #</th>
            <th>Product Name</th>
            <th>Shipping</th>
            <th>UOM</th>
            <th>Unit Price</th>
            <th>Quantity</th>
            <th>Subtotal</th>
        </tr>
        <tr>
            <td>{{ line[&#39;LineNo&#39;] }}</td>
            <td>{{ line[&#39;ItemNo&#39;] }}</td>
            <td>{{ line[&#39;ProductName&#39;] }}</td>
            <td>{{ line[&#39;Shipping&#39;] }}</td>
            <td>{{ line[&#39;UOM&#39;] }}</td>
            <td>US${{ line[&#39;UnitPrice&#39;] }}</td>
            <td>{{ line[&#39;Quantity&#39;] }}</td>
            <td>US${{ line[&#39;Subtotal&#39;] }}</td>
        </tr>
        <tr>
            <td colspan="7">Shipping ({{ line[&#39;text&#39;] }})</td>
            <td>US${{ line[&#39;money&#39;] }}</td>
        </tr>
        <tr>
            <td colspan="7"><b>Order Item Total:</b></td>
            <td>US${{ cart[&#39;total&#39;] }}</td>
        </tr>
    </tbody>
</table>

Notice that not only single quote changes to html code, but the main thing is that loops are moved, so it used to be:

        {% if cart['shipping'] %}
            {% for line in cart['shipping'] %}
                <tr>

but becomes:

{% for line in cart[&#39;cart&#39;] %} {% endfor %} {% if cart[&#39;shipping&#39;] %} {% for line in cart[&#39;shipping&#39;] %} {% endfor %} {% endif %}

Why does CKEditor change the source if these entities are NOT html and I don't do any changes, I don't even focus on the field.

I tried using these CKEditor config options:

CKEDITOR.config.enterMode = CKEDITOR.ENTER_BR;
CKEDITOR.config.entities = false;

CKEDITOR.config.forcePasteAsPlainText = false; // default so content won't be manipulated on load
CKEDITOR.config.basicEntities = true;
CKEDITOR.config.entities = true;
CKEDITOR.config.entities_latin = false;
CKEDITOR.config.entities_greek = false;
CKEDITOR.config.entities_processNumerical = false;
CKEDITOR.config.fillEmptyBlocks = function (element) {
    return true; // DON'T DO ANYTHING!!!!!
};

But I still experience this. Can anyone advise on the config option or any other workaround, except for not using WYSIWYG. I tried to convince users to edit html/twig, but the just want WYSIWYG. Thanks

Was it helpful?

Solution

One possible workaround for me, was to add the Twig blocks to config.protectedSource:

CKEDITOR.config.protectedSource.push(/\{%\s.+\s%\}/g);

They will be ignored in the WYSIWYG editor, but will still be visible in the source code view.

Additionally you can install the plugin Show protected and there's still a visible hint.

OTHER TIPS

working code is :

CKEDITOR.config.protectedSource.push(/{{[\s\S]?}}/g); CKEDITOR.config.protectedSource.push(/{\%[\s\S]?%}/g); CKEDITOR.config.protectedSource.push(/{#[\s\S]*?#}/g);

because we need allow {{ and {% tags for twig

CKEDITOR.config.protectedSource = [
    /\{\{[\s\S]*?\}\}/g,
    /\{\%[\s\S]*?%\}/g,
    /\{\#[\s\S]*?#\}/g,
];

This is why I love Stack Overflow - no matter what you want to ask, someone's probably already asked it! In this case, the answers were close, but for me, adding them as protected source was no good - I wanted to create the twig templates in the string as we use them for Email Templates within our CRM. So, what I did was leave CKEditor to do its thing and then handle it before the template was saved (in our case into the DB, but it could as well be to file).

The function I added is pasted below - feel free to use & abuse as you wish.

This is from our custom Symfony controller onBeforePersist a hook that's called before the entity is persisted... Hopefully its all self explanatory from the code.

Note, the Regex may be a bit dodgy, but appears to work, I'm no Regex expert, so please feel free to suggest a more concise expression.

/**
 * Before we persist the data, we need to clean up any twig tags in there as the editor encodes html entities...
 *
 * @param Request $request
 * @param $form
 * @param EmailTemplates $entity
 */
public function onBeforePersist(Request $request, $form, $entity)
{
    $template = $entity->getView();

    $re = '/\{(\{|%)([^{}]|(?R))*(\}|%)\}/';
    preg_match_all($re, $template, $matches, PREG_SET_ORDER, 0);

    // We only want the first element of each match - I don't like closures as a rule on readability grounds, but this is small enough to be ok.
    array_walk($matches,function(&$value) {
        if (array($value)) {
            $value = $value[0];
        }
    });

    // Now do a replace on them
    foreach ($matches as $match) {
        $decoded = html_entity_decode($match,ENT_QUOTES);
        if ($match != $decoded) {
            // Only replace if we have actually changed the string
            $template = str_replace($match, $decoded, $template);
        }
    }

    // Update the View...
    $entity->setView($template);
}

Similar question CKEditor is escaping html elements

Quick question, the above is a list of config options you have tried one at a time? Having both like this

CKEDITOR.config.entities = false;
CKEDITOR.config.entities = true;

sets entities to true, which is not what you want as it forces html entities in output.

By the way, the other answers are OK but if you have more than one Twig block in the same sentence, are not.

So I definetly recommend to use this Regex instead that works with multiple Twig blocks too:

CKEDITOR.config.protectedSource.push(/\{\{[\s\S]*?\}\}/g);
CKEDITOR.config.protectedSource.push(/\{\%[\s\S]*?%\}/g);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top