Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Editing Twig templates in CKeditor

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

like image 413
Alexey Avatar asked Jan 07 '14 17:01

Alexey


4 Answers

CKEDITOR.config.protectedSource = [
    /\{\{[\s\S]*?\}\}/g,
    /\{\%[\s\S]*?%\}/g,
    /\{\#[\s\S]*?#\}/g,
];
like image 169
472084 Avatar answered Nov 10 '22 16:11

472084


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.

like image 36
dan-lee Avatar answered Nov 10 '22 16:11

dan-lee


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

like image 3
Developer Avatar answered Nov 10 '22 16:11

Developer


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);
}
like image 1
Steve Childs Avatar answered Nov 10 '22 15:11

Steve Childs