Displaying a link in a Twig temple - twig

It seems easy, but I encounter a weird behavior.
In a twig file :
{% set my_html = 'Hello world' %}
{{- true is not same as(false) ? (true is same as(false) ? ('1'~my_html)|raw : ('2'~my_html)|raw) -}}
The printed part is ('2'~my_html)|raw and it works fine : I see a real link.
Output is :
2Hello world
Now, it works only because I apply the raw filter to ('1'~my_html)... Try this :
{% set my_html = 'Hello world' %}
{{- true is not same as(false) ? (true is same as(false) ? ('1'~my_html) : ('2'~my_html)|raw) -}}
And it will display : 2Hello world
I don't understand why I need to apply a filter on something else to get the expected result ? Is it a bug ?

This is a documented behavior of the raw filter. I quote the note from that page:
Be careful when using the raw filter inside expressions:
{% autoescape %}
{% set hello = '<strong>Hello</strong>' %}
{% set hola = '<strong>Hola</strong>' %}
{{ false ? '<strong>Hola</strong>' : hello|raw }}
does not render the same as
{{ false ? hola : hello|raw }}
but renders the same as
{{ (false ? hola : hello)|raw }} {% endautoescape %}
The first ternary statement is not escaped: hello is marked as being
safe and Twig does not escape static values (see escape). In the
second ternary statement, even if hello is marked as safe, hola
remains unsafe and so is the whole expression. The third ternary
statement is marked as safe and the result is not escaped.
And a comment on a github issue clarifies that the concatenation operator marks your string as unsafe. So in your case
{% set my_html = '<' %}
{# ('1'~my_html) is not safe, so the whole expression is not #}
{{ false
? ('1'~my_html)
: ('2'~my_html)|raw
}}
includes two strings: a safe one, ('2'~my_html)|raw) and an unsafe one, ('1'~my_html) (because it does not apply the raw filter), so as the note from raw documentation says, the whole expression stays unsafe and autoescaping is applied. But in the other case when both strings are marked safe, the whole expression becomes safe and the autoesaping is not applied:
{% set my_html = '<' %}
{# now both strings are safe, so is the whole expression #}
{{ false
? ('1'~my_html)|raw
: ('2'~my_html)|raw
}}

This is not a bug but, due to the fact the default settings of twig will autoescape variables.
You can read more about it in the documentation.

Related

If statement with string length check

I'm trying to check if a string is empty in a twig template but I don't understand this behaviour. I want to show a textarea element when the condition is met. Here's the code:
{% if item.payload.customizationText|length == 0 %}
<textarea class="customization-text">{{ item.payload.customizationText|trim }}</textarea>
{% endif %}
It always shows the textarea, whether the string has something in it or not. Am I missing something?
It's in shopware 6 by the way and the dump() fuction throws an error because it's undefined.
Also, this expression just outputs the string. Is the length expression not available?
{{ item.payload.customizationText|length }}
I believe there is a mistake in the question. It does not make sense to print the variable, only if it is empty.
If you want to show the text area in case the customizationText has some contents, you should use this:
{% if item.payload.customizationText|trim|length %}
<textarea class="customization-text">{{ item.payload.customizationText|trim }}</textarea>
{% endif %}
https://twigfiddle.com/nmk2kq/4
You can try it.
{% if item.payload.customizationText is defined and item.payload.customizationText is empty %}
<textarea class="customization-text">{{ item.payload.customizationText|trim }}</textarea>
{% endif %}

"Illegal offset type in isset or empty" when using a Twig macro

Using Grav v1.3.8 (running on PHP 5.6.30), I'm currently getting a Server error ("Illegal offset type in isset or empty") when trying to render a Twig template that is using a macro.
What's interesting is that this only happens when I use the macro by itself. As soon as I append a filter, like trim, everything works as expected.
The (shortened) macro file, helpers.twig:
{% macro ascii(str) %}
{% spaceless %}
{{ str|replace({
'Á': 'A',
'À': 'A',
'Â': 'A',
'Ã': 'A',
....
'ƒ': 'f'
})
}}
{% endspaceless %}
{% endmacro ascii %}
The template (MCVE):
{% import 'macros/helpers.twig' as helpers %}
{% set img = helpers.ascii('günter-berger.jpg') %}
{% if page.media[img] is defined %}
<img src="{{ page.media[img].url }}">
{% endif %}
This will produce the error. I narrowed it down to the if line. Apparently, the macro is working fine, but the condition will throw an error if fed the output of it, unfiltered. Adding any filter, like trim or lower, will get it to work again.
In other words, these work:
{% if page.media['günter-berger.jpg'] is defined %}
{% if page.media[helpers.ascii('günter-berger.jpg')|trim] is defined %}
But this will throw an error:
{% if page.media[helpers.ascii('günter-berger.jpg')] is defined %}
However, trying the same thing on twigfiddle, all three seem to work there.
Maybe an issue with Grav? Can someone point out any possible causes?
I forgot this, but a macro does not return a string but instead returns an instance of a Twig_Markup
{% set test = macro.ascii('Ghünter.jpg') %}
{{ dump(test) }}
Output : object(Twig_Markup)#10679 (2) { ["content":protected]=> string(11) "Ghunter.jpg" ["charset":protected]=> string(5) "UTF-8" }
Because the return type is an object you get this notification as you can't use objects as index. By using a filter on this instance, the magic method __toString method will be called, causing it to return a string, thus making it useable as index for an array
The only was to bypass this, would be writing a filter instead of a macro

Twig IF statement not working

I am trying to use a simple if statement in Twig. However it doesn't seem to be working properly. The variable emailActivation is certainly being loaded into Twig properly as can be seen from the first line of the output in all cases.
However the if statement is behaving very oddly - for example it works as expected when the variable is false and being tested against 1 (case 2) but not when it is false and tested against 'false' (case 1).
Conversely when the variable is true the if statement works when the variable is tested against 'true' (case 3) but not when tested against 1 (case 4).
I only show the tests against 1 as a workaround I attempted, really I think the problem lies in (case 1). (case 5) and (case 4) suggest the numbers are being evaluated as boolean.
I also thought it could perhaps be an issue to do with caching, but to my knowledge I have set Twig up not to use a cache. Even if Twig is caching the problem still occurs on the first page load and when the browser is restarted. I have tried in IE and Firefox though again I don't think the browser is really relevant to the problem which lies in Twig itself as far as I can tell.
Thanks in advance for any assistance!
Case 1 emailActivation = false
Twig code
{{ emailActivation }}
{% if emailActivation == true %}
feature disabled
{% else %}
other content
{% endif %}
Output
false
feature disabled
Case 2 emailActivation = false
Twig code
{{ emailActivation }}
{% if emailActivation == 1 %}
feature disabled
{% else %}
other content
{% endif %}
Output
false
other content
Case 3 emailActivation = true
Twig code
{{ emailActivation }}
{% if emailActivation == true %}
feature disabled
{% else %}
other content
{% endif %}
Output
true
feature disabled
Case 4 emailActivation = true
Twig code
{{ emailActivation }}
{% if emailActivation == 1 %}
feature disabled
{% else %}
other content
{% endif %}
Output
true
other content
Case 5 emailActivation = true
Twig code
{{ emailActivation }}
{% if emailActivation == 0 %}
feature disabled
{% else %}
other content
{% endif %}
Output
true
feature disabled
One detail make think that emailActivation could be a string.
The output of {{ emailActivation }} is false or true, but I think that a boolean return something else (~ nothing or 1 ~).
The fact to test the condition as being a string should resolve the problem.
One way will be (for simply a string, not a big content):
{{ emailActivation }}
{{ (emailActivation == 'true') ? 'feature disabled' : 'other content' }}
Friendly
try same as it is equivalent to === in PHP
http://twig.sensiolabs.org/doc/tests/sameas.html

Twig ternary not applying filter if false

When using Twig's ternary operator, everything goes as expected EXCEPT the |raw filter is not being applied when the condition is false:
{{ thing.description|length > 255 ? thing.description|striptags|slice(0,255) ~ '...' : thing.description|raw }}
When true, it IS applying the |striptags|slice(0,255) filters.
The |raw filter does get applied when coded like this (and false):
{% if thing.description|length > 255 %}
{{ thing.description|striptags|slice(0,255) ~ '...' }}
{% else %}
{{ thing.description|raw }}
{% endif %}
For the life of me I can't figure out why |raw isn't applied when using ternary syntax.
In fact |raw do nothing. Really. Look at its code here.
When you do operation, value is marked as unsafe. |raw mark it as safe. If you try to print unsafe value with {{ ... }}, it will be escaped. It's how Twig is designed.
Let's analyse that simple code: {{ (a|raw) ~ (b|raw) }}
a is marked as safe.
b is marked as safe.
a and b are glued together. It's marked as unsafe, because ~ is a operation.
Value a ~ b is printed ESCAPED. Wow.
You must use {{ (a ~ b)|raw }}.
In your problem: first you calculate thing.description|raw and then it's passed to ternary. Results are marked as unsafe, because ternary is operation. You must use |raw after all operations, so {% if %} is the only solution.

Minus In twig block definition

What's the difference between this:
{%block body %}
and that
{%block body -%}
Just read something about it in the documentation, not sure if this will also apply on {% block ... %} tags.
Twig whitespace control
{% set value = 'no spaces' %}
{#- No leading/trailing whitespace -#}
{%- if true -%}
{{- value -}}
{%- endif -%}
{# output 'no spaces' #}
There's also another example given which trims the whitespace in front of the variable but doesnt't do it at the end - so the effect is only on one side.
{% set value = 'no spaces' %}
<li> {{- value }} </li>
{# outputs '<li>no spaces </li>' #}
The above sample shows the default whitespace control modifier, and how you can use it to remove whitespace around tags. Trimming space will consume all whitespace for that side of the tag. It is possible to use whitespace trimming on one side of a tag
So I think the difference in your given exmaples is that in the first block body there will be a whitespace after the block started. In your second example body - there's none after the block started.
Just read the documentation entry to see how it works.
EDIT
A simple example to demonstrate the example in the docu:
{% set value = 'NO space in source code after/before "value"' %}
<li> {{- value -}} </li>
...
outputs in Firebug in the HTML markup:
Whereas this
{% set value = 'space in source code after "value"' %}
<li> {{- value }} </li>
...
ouputs:
Notice the space between "value" and the closing </li> in the second example. So the minus - erases/trims a whitespace either before, after or on both sides of e.g. a variable.

Resources