Editing Twig templates in CKeditor - symfony

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['cart'] %} {% endfor %} {% if cart['shipping'] %} {% for line in cart['shipping'] %} {% 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['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>
<tr>
<td colspan="7">Shipping ({{ line['text'] }})</td>
<td>US${{ line['money'] }}</td>
</tr>
<tr>
<td colspan="7"><b>Order Item Total:</b></td>
<td>US${{ cart['total'] }}</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['cart'] %} {% endfor %} {% if cart['shipping'] %} {% for line in cart['shipping'] %} {% 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

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.

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);

Related

How to force a page-break on weasyprint?

I wonder if its possible to create a force break that splits the table rows? I think weasyprint does this in a sense but I want to break the rows in to specific counts (e.g 6 rows per page). Is that possible? I tried something like this:
<table>
<tr>
<th>Header1</th>
<th>Header2</th>
</tr>
{% for d in data %}
{% if forloop.counter == 6 %}
<p style="page-break-before: always;"></p>
{% endif %}
<tr>
<td>{{ d.name }}</td>
<td>{{ d.age }}</td>
</tr>
{% endfor %}
</table>
but it just break each row in different pages. Any help would be appreciated.
Try with style attribute on tr element without adding any p element :
<table>
<tr>
<th>Header1</th>
<th>Header2</th>
</tr>
{% for d in data %}
<tr
{% if forloop.counter == 6 %}
style="break-before: page;"
{% endif %}
>
<td>{{ d.name }}</td>
<td>{{ d.age }}</td>
</tr>
{% endfor %}
</table>
Note that page-break-before is deprecated : MDN.
Note also that the result may not be really nice, you may have to add margin-top or something, and also some border at the beginning and end of each block of rows.

How to get the number of result? - Pagerfanta and Twig (in Symfony)

In my Demo application, use Pagerfanta to read only a few rows from Database.
public function findAllProductsByCategory($category, int $page = 1): Pagerfanta
{
// DQL = Always select full classes from classes
$query = $this->getEntityManager()
->createQuery('
SELECT p
FROM App\Entity\Product p
WHERE p.category = :category
')
->setParameter('category', $category);
return $this->createPaginator($query, $page);
}
Later in Twig, I loop the preselected results. Everything is fine ...
{% if products.haveToPaginate %}
<div class="navigation text-center">
{{ pagerfanta(products, 'default', {routeName: 'category_show_paginated', 'routeParams' : { 'id': category.id}}) }}
</div>
{% endif %}
<table border="1" cellpadding="5">
<tr>
<th>#</th>
<th>Name</th>
<th>Products</th>
<th>Description</th>
</tr>
{% for product in products %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ product.name }}</td>
<td>{{ product.description }}</td>
</tr>
{% endfor %}
</table>
but on each page, "loop.index" is the same 1,2,3,4 ...
I would like to show the number of results 41,42,43 ... with pagerfanta ;)
Is there a special function?
Thank you
Haven't worked with Pagerfanta for a while, but what you want should be doable with something like this:
{{ pagerfanta.currentPageOffsetStart() + loop.index }}
Example test case: https://hotexamples.com/examples/pagerfanta/Pagerfanta/getCurrentPageOffsetStart/php-pagerfanta-getcurrentpageoffsetstart-method-examples.html

Accessing symfony form collection prototype attributes/properties inside twig block

I am trying to setup a custom symfony form collection prototype component of a form. I am referencing the proper documentation
https://symfony.com/doc/3.3/form/form_customization.html#how-to-customize-a-collection-prototype
The form collection is setup using the following
<table class="table table-bordered" data-prototype="{{ form_row(form.quoteItemDeliverables.vars.prototype)|e('html_attr') }}">
<caption>Deliverables</caption>
<tr>
<th>Quantity</th>
<th>Date Required</th>
</tr>
{% for itemDeliverable in form.quoteItemDeliverables %}
<tr>
<td>{{ form_widget(itemDeliverable.quantity) }}</td>
<td>{{ form_widget(itemDeliverable.dateRequired) }}</td>
</tr>
{% endfor %}
</table>
The twig block that is referencing the prototype is setup using the following
{% form_theme form _self %}
{% block _uniflytebundle_quoteitem_quoteItemDeliverables_entry_row %}
<tr>
{#<td>{{ form_widget(form.quoteItemDeliverables.vars.prototype.quantity) }}</td>#}
</tr>
{{ dump(form.children["quoteItemDeliverables"]) }}
{% endblock %}
The dump(); is returning the following error
Key "quoteItemDeliverables" for array with keys "quantity, dateRequired" does not exist.
What am I doing wrong?
If I dump(form); I get the form object displaying the children, the "quoteItemDeliverables" and the "prototype" elements
Can someone please point me in the right direction on how to access the various form properties? Trying to do what was done in the collection form for the prototype. Below being working form collection elements.
<td>{{ form_widget(itemDeliverable.quantity) }}</td>
<td>{{ form_widget(itemDeliverable.dateRequired) }}</td>
I would like the same for the prototype twig block but using something like
form_widget(itemDeliverable.prototype.dateRequired)
does not work. How can I do this?
Thank you in advance for your time and effort invested.
Inside the twig block, form does not refer to the "global" form but to the sub form corresponding to the entry of your collection. That is why the error produced by the dump call indicates that the array has quantity and dateRequired keys but no quoteItemDeliverables.
Thus what you should have is more likely to be something like:
{% block _uniflytebundle_quoteitem_quoteItemDeliverables_entry_row %}
<tr>
<td>{{ form_widget(form.quantity) }}</td>
<td>{{ form_widget(form.dateRequired}}</td>
</tr>
{% endblock %}

An attribute is not displayed in twig

I have an attribute immobilier.ref that is not displayed with twig
this is the code
{% for immobilier in listImmobilier %}
<tr>
<td>{{ immobilier.id }}</td>
<td>{{ immobilier.ref }}</td>
<td>{{ immobilier.titre }}</td>
<td><span class="glyphicon glyphicon-eye-open" style="display:block; text-align:center"></span></td>
<td>Edit</td>
<td>Delete</td>
</tr>
{% endfor %}
this is a photo of the table
It(s fine now, it was because I have a function named ref in the entity
/**
* #ORM\PostPersist()
*/
public function ref()
{
$id = $this->getId();
$operation = $this->getOperation()->getRef();
$this->setRef($id.$operation);
}
I have changed her name and it work fine now.

Tables and forms in Twig

I have a table-form which I implemented this way:
<tr>
<td>Start Date:</td>
<td>{{ form_widget(form.start_date) }}</td>
</tr>
<tr>
<td>Previous Plan:</td>
<td>{{ form_widget(form.prev_plan) }}</td>
</tr>
Is there a way to make this using the ready theme form_table_layout.html.twig or at all in some more flexible and elegant way?
I tried this:
{% form_theme form 'form_table_layout.html.twig' %}
{{ form_errors(form) }}
{% for field in form %}
{{ form_row(field) }}
{% endfor %}
but it puts its own names in the left part of the table which are not the names I want. (For example instead of "Previous Plan: " in this way I got "Prev plan")
You can define your own labels when defining the form. Something like:
->add('prev_plan', 'text',array(
'label' => 'Previous Plan'
))
(I don't know the field type for prev_plan, I used 'text' but if it is a different field type just change that)

Resources