Mocking Twig Function with namespace - php

I am writing a unit test with Codeception to test a twig template. However, I am unable to find a way to mock the block.richTextFields.all() function. The block.richTextField is passed by the Twig context in this case so my initial thought was to create TwigFunction to mock it, but I can't find a way to have all function inside block.richTextField namespace.
{% for text in block.richTextFields.all() %}
{% if (loop.length < 3 and loop.length > 1 ) or (loop.length == 4) or (loop.length < 3 and loop.index == 2) or (loop.length < 6 and (loop.index == 4 or loop.index == 5)) %}
{% set topRowColWidth = "col-lg-6 margin-top-10" %}
{% elseif loop.length == 1 and loop.last %}
{% set topRowColWidth = "col-lg-12 margin-top-10" %}
{% else %}
{% set topRowColWidth = "col-lg-4" %}
{% endif %}
<div class="{{ topRowColWidth }} dynamic-rich-text">
{% if text.richTextField|length %}
{{text.richTextField}}
{% endif %}
</div>
{% endfor %}

Related

How to render twig condition dynamically from php array

How to render twig condition dynamically from php array
Php data array
arry('label'=>'test','parentRoleExp'=>"is_granted('ROLE_ADMIN') OR is_granted('ROLE_BLOG')"
twig code
{% set parentRoleExp = '' %}
{% if link['parentRoleExp'] is defined %}
{% set parentRoleExp = link['parentRoleExp'] %}
{% endif %}
{% if parentRoleExp %}
<h1>Admin Blog</h1>
{% else %}
<h1>Blog <?h1>
{% endif %}
Above code not working
Expected result
{% if is_granted('ROLE_ADMIN') OR is_granted('ROLE_BLOG') %}
<h1>Admin Blog</h1>
{% else %}
<h1>Blog <?h1>
{% endif %}
I tried with various method but not working. please help
Thanks in advance
it's very bad idea - ulnerability
But if you want... register eval() function:
$twig = new Twig\Environment($loader);
$twig->addFunction(new Twig\TwigFunction('phpEval', 'eval'));
in template
{% if phpEval(parentRoleExp) %}

How to set a custom class to submenus in Drupal 8?

I am working on sidebar menu in a Custom Drupal 8 Theme. I am trying to set a class of sidebar__menu--submenu-1,sidebar__menu--submenu-2, sidebar__menu--submenu-3 and so on depending on the submenu's level.
So far, I was able to add the class sidebar__menu to the first level & sidebar__menu--submenu to all submenu's level. However, I want to add the 'class' sidebar__menu--submenu-(number of the level) so I can style & control the sidebar better with CSS.
Here it is my code menu.html.twig:
{{ menus.menu_links(items, attributes, 0) }}
{% macro menu_links(items, attributes, menu_level, menu_name) %}
{% import _self as menus %}
{%
set menu_classes = [
'sidebar__menu' ~ menu_name|clean_class,
]
%}
{%
set submenu_classes = [
'sidebar__menu' ~ menu_name|clean_class ~ '--submenu',
]
%}
{% if items %}
{% if menu_level == 0 %}
<ul{{ attributes.addClass('container mx-auto', menu_classes) }}>
{% else %}
<ul {{ attributes.removeClass(menu_classes).addClass(submenu_classes) }}>
{% endif %}
{% for item in items %}
{%
set classes = [
'sidebar__item',
item.is_expanded ? 'sidebar__item--expanded',
item.is_collapsed ? 'sidebar__item--collapsed',
item.in_active_trail ? 'sidebar__item--active-trail',
]
%}
<li{{ item.attributes.addClass(classes) }}>
{{ link(item.title, item.url) }}
{% if item.below %}
{{ menus.menu_links(item.below, attributes, menu_level + 1) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
Any help will be really appreciate it!
I have found the answer. First we set the classes:
{%
set submenu_classes = [
'sidebar__menu' ~ menu_name|clean_class ~ '--submenu',
'sidebar__menu--submenu-' ~ (menu_level + 1),
]
%}
Then using the logic like so:
{% if menu_level == 0 %}
<ul{{ attributes.addClass('container mx-auto', menu_classes) }}>
{% else %}
<ul{{ attributes.removeClass(menu_classes, 'sidebar__menu--submenu-' ~ (menu_level)).addClass(submenu_classes) }}>
{% endif %}

Twig/PHP/Symfony nesting error - nesting level too deep

I have a (very big) twig html template, where I display a table and loop through the rows:
{% for assignment in assignments %}
...
{% if assignment.zip.contractor1 is not null %}
{% for priceEK in assignment.zip.contractor1.pricecontractor %}
{% if priceEK.zip == assignment.zip %}
{% set bookDates = date(assignment.start|date).diff(assignment.end).days + 1 %}
{% set priceDay = 0 %}
{% set priceSide = 0 %}
{% if bookDates > 1 %}
{% set priceDay = priceEK.priceDay * (bookDates - 1) %}
{% set priceSide = priceEK.priceAdd * (bookDates) %}
{% endif %}
{% if assignment.meter == 15 %}
{{ (priceEK.price15 + priceDay + priceSide)|number_format(2) }}€
{% elseif assignment.meter == 20 %}
{{ (priceEK.price20 + priceDay + priceSide)|number_format(2) }}€
{% else %}
{{ (priceEK.price25 + priceDay + priceSide)|number_format(2) }}€
{% endif %}
{% endif %}
{% endfor %}
{%endif %}
Now this works like a charm IF, assignment.zip.contractor1 is not null is only true ONE TIME. If it is true a second time while looping through assignments it gives me the following error in my logs:
Error: Nesting level too deep - recursive dependency?
I assume this may be a complicated problem without knowing the database relations, please feel free letting me know what further info is needed (and how to insert here), I'll update accordingly.
//EDIT Maybe there is a way to 'reset' the nested for (priceEK)? {% set priceEK = null %} has no effect though...
//EDIT2: Found sth. else, changed all == to is same as() as described HERE. Now I can get a second loop. If I then have a third one, Firefox crashes and wants to debug the script...
//EDIT: ok, found a solution... Maybe this helps for someone else:
{% for pc in assignment.zip.pricecontractor %}
{% if pc.contractor is same as(assignment.zip.contractor1) %}
{% set bookDates = date(assignment.start|date).diff(assignment.end).days + 1 %}
{% set priceDay = 0 %}
{% set priceSide = 0 %}
{% if bookDates > 1 %}
{% set priceDay = pc.priceDay * (bookDates - 1) %}
{% set priceSide = pc.priceAdd * (bookDates) %}
{% endif %}
{% if assignment.meter is same as(15) %}
{{ (pc.price15 + priceDay + priceSide)|number_format(2) }}€
{% elseif assignment.meter is same as(20) %}
{{ (pc.price20 + priceDay + priceSide)|number_format(2) }}€
{% else %}
{{ (pc.price25 + priceDay + priceSide)|number_format(2) }}€
{% endif %}
{% endif %}
{% endfor %}

Twig - interpolating variables

I have the following:
{% if promo.monday_unavailable == 1 %}
not available mondays
{% elseif promo.monday_available == 1%}
available mondays
{% else %}
available mondays from {{promo.monday_start}} until {{promo.monday_end}}
{% endif %}
<br />
{% if promo.tuesday_unavailable == 1 %}
not available tuesdays
{% elseif promo.tuesday_available == 1%}
available tuesdays
{% else %}
available tuesdays from {{promo.tuesday_start}} until {{promo.tuesday_end}}
{% endif %}
<br />
...
That I would like to do for each day of the week.
I'm wondering if there is a way I can simplify the code to read
{% for i in ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] %}
{% if promo.~i~"_unavailable" == 1 %}
not available mondays
{% elseif promo.~i~"_available" == 1%}
available mondays
{% else %}
available mondays from {{promo.~i~"_start"}} until {{promo.~i~"_end"}}
{% endif %}
<br />
{% endfor %}
With Twig.
Any help would be appreciated. I'm at a loss for what keywords to search for anymore.
I know this is an old thread but twig has support for inline interpolation like:
{{i18n("language_#{langId}")}}
Important that the string to interpolated is with double-quotes.
Found answer by mashing my forehead on the keyboard.
rather than
{% if promo.~i~"_unavailable" == 1 %}
use
{% promo[i~"_unavailable"] == 1 %)
You can try using my code
{% for i in ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] %}
{% set key = i ~ '__unavailable' %}
{% if (promo[key]) eq something %}
//
{% endif %}
{% endfor}

How can I use break or continue within for loop in Twig template?

I try to use a simple loop, in my real code this loop is more complex, and I need to break this iteration like:
{% for post in posts %}
{% if post.id == 10 %}
{# break #}
{% endif %}
<h2>{{ post.heading }}</h2>
{% endfor %}
How can I use behavior of break or continue of PHP control structures in Twig?
This can be nearly done by setting a new variable as a flag to break iterating:
{% set break = false %}
{% for post in posts if not break %}
<h2>{{ post.heading }}</h2>
{% if post.id == 10 %}
{% set break = true %}
{% endif %}
{% endfor %}
An uglier, but working example for continue:
{% set continue = false %}
{% for post in posts %}
{% if post.id == 10 %}
{% set continue = true %}
{% endif %}
{% if not continue %}
<h2>{{ post.heading }}</h2>
{% endif %}
{% if continue %}
{% set continue = false %}
{% endif %}
{% endfor %}
But there is no performance profit, only similar behaviour to the built-in break and continue statements like in flat PHP.
From docs TWIG 2.x docs:
Unlike in PHP, it's not possible to break or continue in a loop.
But still:
You can however filter the sequence during iteration which allows you to skip items.
Example 1 (for huge lists you can filter posts using slice, slice(start, length)):
{% for post in posts|slice(0,10) %}
<h2>{{ post.heading }}</h2>
{% endfor %}
Example 2 works TWIG 3.0 as well:
{% for post in posts if post.id < 10 %}
<h2>{{ post.heading }}</h2>
{% endfor %}
You can even use own TWIG filters for more complexed conditions, like:
{% for post in posts|onlySuperPosts %}
<h2>{{ post.heading }}</h2>
{% endfor %}
A way to be able to use {% break %} or {% continue %} is to write TokenParsers for them.
I did it for the {% break %} token in the code below. You can, without much modifications, do the same thing for the {% continue %}.
AppBundle\Twig\AppExtension.php:
namespace AppBundle\Twig;
class AppExtension extends \Twig_Extension
{
function getTokenParsers() {
return array(
new BreakToken(),
);
}
public function getName()
{
return 'app_extension';
}
}
AppBundle\Twig\BreakToken.php:
namespace AppBundle\Twig;
class BreakToken extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
// Trick to check if we are currently in a loop.
$currentForLoop = 0;
for ($i = 1; true; $i++) {
try {
// if we look before the beginning of the stream
// the stream will throw a \Twig_Error_Syntax
$token = $stream->look(-$i);
} catch (\Twig_Error_Syntax $e) {
break;
}
if ($token->test(\Twig_Token::NAME_TYPE, 'for')) {
$currentForLoop++;
} else if ($token->test(\Twig_Token::NAME_TYPE, 'endfor')) {
$currentForLoop--;
}
}
if ($currentForLoop < 1) {
throw new \Twig_Error_Syntax(
'Break tag is only allowed in \'for\' loops.',
$stream->getCurrent()->getLine(),
$stream->getSourceContext()->getName()
);
}
return new BreakNode();
}
public function getTag()
{
return 'break';
}
}
AppBundle\Twig\BreakNode.php:
namespace AppBundle\Twig;
class BreakNode extends \Twig_Node
{
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("break;\n")
;
}
}
Then you can simply use {% break %} to get out of loops like this:
{% for post in posts %}
{% if post.id == 10 %}
{% break %}
{% endif %}
<h2>{{ post.heading }}</h2>
{% endfor %}
To go even further, you may write token parsers for {% continue X %} and {% break X %} (where X is an integer >= 1) to get out/continue multiple loops like in PHP.
From #NHG comment — works perfectly
{% for post in posts|slice(0,10) %}
I have found a good work-around for continue (love the break sample above).
Here I do not want to list "agency". In PHP I'd "continue" but in twig, I came up with alternative:
{% for basename, perms in permsByBasenames %}
{% if basename == 'agency' %}
{# do nothing #}
{% else %}
<a class="scrollLink" onclick='scrollToSpot("#{{ basename }}")'>{{ basename }}</a>
{% endif %}
{% endfor %}
OR I simply skip it if it doesn't meet my criteria:
{% for tr in time_reports %}
{% if not tr.isApproved %}
.....
{% endif %}
{% endfor %}

Categories