Variable Variable in Twig - twig

I have the following variables in twig:( I can see them with kint)
data.po_user_setting.us_highlight_color1 = '#008080'
data.po_user_setting.us_highlight_color2 = '#00FFFF'
data.po_user_setting.us_highlight_color3 = '#FFFF00'
data.po_user_setting.us_highlight_color4 = '#FF0000'
data.po_user_setting.us_highlight_color5 = '#FF00FF'
and
verse.po_verse_highlight.hl_rating = Returns [1-5]
How can I show the dynamic variable like this? Neither of these lines work:
{{_context['data.po_user_setting.us_highlight_color' ~ verse.po_verse_highlight.hl_rating]}}
{{attribute(_context, 'data.po_user_setting.us_highlight_color' ~ verse.po_verse_highlight.hl_rating)}}

The problem you are facing is the variable you want to access is actually an array.
With your current code, twig is looking for a variable called e.g., data.po_user_setting.us_highlight_color.foo, translated to $_context['data.po_user_setting.us_highlight_color.foo']
To actually access the variable you want, you would need to treat the variable like an array:
{{ {{ _context['data']['po_user_setting']['us_highlight_color'~ verse.po_verse_highlight.hl_rating] }} }}
This is quite long to type every time, so to reduce the typing you could use a macro or extend twig
macro
{% macro get_array_value(context, key) %}
{% set value = null %}
{% for key in key|split('.') %}
{% set value = loop.first ? context[key]|default : value[key]|default %}
{% endfor %}
{{ value }}
{% endmacro %}
As macro's don't have access to the special variable _context you would to pass this every time when you want to call the macro, e.g.
{% import _self as macros %}
{{ macros.get_array_value(_context, 'data.po_user_setting.us_highlight_color' ~ verse.po_verse_highlight.hl_rating) }}
extending twig
<?php
$twig->addFunction(new \Twig\TwigFunction('get_array_value', function ($context, $variable) {
$keys = explode('.', $variable);
if (empty($keys)) return;
$value = $context[array_shift($keys)] ?? [];
foreach($keys as $key) {
$value = $value[$key] ?? [];
}
return !empty($value) ? $value : null;
}, ['needs_context' => true,]));
Then you can call this function like the following in twig
{{ get_array_value('data.po_user_setting.us_highlight_color' ~ verse.po_verse_highlight.hl_rating) }}

Related

TWIG - include variables in different template

I would like to include the same variables in different templates
vars_catchphrase.twig
{% set catchphrase_size = '' %}
{% if var.tile_catchphrase|length <= 4 %}
{% set catchphrase_size = 'size-lg' %}
{% elseif var.tile_catchphrase|length >= 5 and var.tile_catchphrase|length <= 8 %}
{% set catchphrase_size = 'size-md' %}
{% elseif var.tile_catchphrase|length >= 9 and var.tile_catchphrase|length <= 12 %}
{% set catchphrase_size = 'size-sm' %}
{% elseif var.tile_catchphrase|length >= 13 %}
{% set catchphrase_size = 'size-xs' %}
{% endif %}
I tried to include with this (because the context is sometime different) :
{% include 'vars_catchphrase.twig' with { 'var' : post } %}
When the context is different from post I use another one :
{% include 'vars_catchphrase.twig' with { 'var' : item } %}
example.twig
{% for item in list %}
{% include 'vars_catchphrase.twig' with { 'var' : item } %}
<p class="catchphrase {{ catchphrase_size }}">{{ item.title }}</p>
{% endfor %}
The variable is empty. Can I have some help please ?
Templates you include have their own variable scope, this means variables defined inside this template will not be known out the template. This said, included templates also can't alter the parent's context (by default), this is due to twig passing the context array by value, not by reference.
foo.twig
{% set foo = 'foo' %}
{% include 'bar.twig' %}
{{ foo }}
bar.twig
{% set foo = 'bar' %}
The example above will still output foo
In order to solve your problem, I'd suggest adding a custom filter to twig
<?php
$twig->addFilter(new \Twig\TwigFilter('catchphrase_size', function($value) {
switch(true) {
case strlen($value->tile_catchphrase) >= 13: return 'size-xs';
case strlen($value->tile_catchphrase) >= 9: return 'size-sm';
case strlen($value->tile_catchphrase) >= 5: return 'size-md';
default: return 'size-lg';
}
});
This way you can use the filter where ever,
{% for item in list %}
<p class="catchphrase {{ item|catchphrase_size }}">{{ item.title }}</p>
{% endfor %}

How can I search for the search query value from multiple specified fields in Craft CMS?

In Craft CMS I want to search for the search query value for only some fields/ multiple fields - but not all.
For example limiting to the fields title, introduction, cardContent.
I've added a search: property to to my queryEntry object with the value of title and the query string. But I would like to add more fields.
{% set searchQuery = craft.app.request.getParam('q') %}
{# {% set queryEntries = craft.entries({
section: queryFilters
}).search(searchQuery) %} #}
{% set queryEntries = craft.entries({
search: 'title:' ~ searchQuery,
order: 'score'
}) %}
{% if craft.app.request.getParam('q') %}
{% set searchQuery = '"' ~ craft.app.request.getParam('q') ~ '"' %}
{% set queryEntries = craft.entries({
search: 'title:' ~ searchQuery ~ ' OR cardContent:' ~ searchQuery ~ ' OR introduction:' ~ searchQuery ,
order: 'score'
}) %}
{% endif %}
Get the query string
add searchTerms is concatinated in a string using OR and the query
This returns the array of entries matching the queryEntries.search and you can do what you like with this - eg loop over and display results
You can concatenate any number of fields with their value in the variable and then you can simple pass that in the search parameter with entries query. Here is the example code for that.
{% set nameparam = craft.app.request.getParam('data') %}
{% set categoryparam = craft.app.request.getParam('data1') %}
{% set queryString = '' %}
{% if nameparam is defined and nameparam is not empty %}
{% set queryString = queryString ~ 'title:*'~nameparam~'* ' %}
{% endif %}
{% if categoryparam is defined and categoryparam is not empty %}
{% set queryString = queryString ~ 'blogCategory:'~categoryparam~' ' %}
{% endif %}
{% if queryString is defined and queryString is not empty %}
{% set queryParams = {
search: {
query: queryString,
order: 'score'
},
} %}
{% else %}
{% set queryParams = {} %}
{% endif %}
{% set queryEntries = craft.entries(queryParams) %}

Symfony & Twig: how to get vars in twig by DB data?

One question please.
{{ dump(app.user.slugName) }}
If I do the above snippet in Twig, I get the slugName of the user loged ("my-user-2", i.e.) in the app (SlugName is an atribute of the entity user). Ok & Correct. But... I want to order this action from a var (var from BD data)
I have a variable named option which is set like this:
{% set option = 'app.user.slugName' %}
But when I'm trying output this variable with {{ dump(option)}} it returns app.user.slugName as literal. It does not return my-user-2.
Is there are any way in twig to solve this? It's a function to generate a menu, but some links needs some parameters.
I see what you mean, but Twig can't evaluate expression like that.
To achieve something like that you would need a snippet like this,
{% set value_methods = 'app.user.slugname' %}
{% set option_value = _context %}
{% for method in (value_methods|split('.')) if method != '' %}
{% set option_value = attribute(option_value, (method|replace({'()': '', }))) %}
{% endfor %}
{{ option_value }}
twigfiddle
(edit)
Remember you can create a macro to achieve some reusability for this snippet,
{% import _self as macros %}
{{ macros.evaluate(_context, 'app.user.slugname') }}
{% macro evaluate(context, value_methods) %}
{% set option_value = context %}
{% for method in (value_methods|split('.')) if method != '' %}
{% set option_value = attribute(option_value, (method|replace({'()': '', }))) %}
{% endfor %}
{{ option_value }}
{% endmacro %}

Check if variable is string or array in Twig

Is it possible to check if given variable is string in Twig ?
Expected solution:
messages.en.yml:
hello:
stranger: Hello stranger !
known: Hello %name% !
Twig template:
{% set title='hello.stranger' %}
{% set title=['hello.known',{'%name%' : 'hsz'}] %}
{% if title is string %}
{{ title|trans }}
{% else %}
{{ title[0]|trans(title[1]) }}
{% endif %}
Is it possible to do it this way ? Or maybe you have better solution ?
Can be done with the test iterable, added in twig1.7, as Wouter J stated in the comment :
{# evaluates to true if the users variable is iterable #}
{% if users is iterable %}
{% for user in users %}
Hello {{ user }}!
{% endfor %}
{% else %}
{# users is probably a string #}
Hello {{ users }}!
{% endif %}
Reference : iterable
Ok, I did it with:
{% if title[0] is not defined %}
{{ title|trans }}
{% else %}
{{ title[0]|trans(title[1]) }}
{% endif %}
Ugly, but works.
I found iterable to not be good enough since other objects can also be iterable, and are clearly different than an array.
Therefore adding a new Twig_SimpleTest to check if an item is_array is much more explicit. You can add this to your app configuration / after twig is bootstrapped.
$isArray= new Twig_SimpleTest('array', function ($value) {
return is_array($value);
});
$twig->addTest($isArray);
Usage becomes very clean:
{% if value is array %}
<!-- handle array -->
{% else %}
<!-- handle non-array -->
{% endif % }
There is no way to check it correctly using code from the box.
It's better to create custom TwigExtension and add custom check (or use code from OptionResolver).
So, as the result, for Twig 3, it will be smth like this
class CoreExtension extends AbstractExtension
{
public function getTests(): array
{
return [
new TwigTest('instanceof', [$this, 'instanceof']),
];
}
public function instanceof($value, string $type): bool
{
return ('null' === $type && null === $value)
|| (\function_exists($func = 'is_'.$type) && $func($value))
|| $value instanceof $type;
}
}
Assuming you know for a fact that a value is always either a string or an array:
{% if value is iterable and value is not string %}
...
{% else %}
...
{% endif %}
This worked good enough for me in a project I was working on. I realize you may need another solution.

Using twig variable to dynamically call an imported macro sub-function

I am attempting if use a variable to call a specific macro name.
I have a macros file that is being imported
{% import 'form-elements.html.twig' as forms %}
Now in that file there are all the form element macros: text, textarea, select, radio etc.
I have an array variable that gets passed in that has an elements in it:
$elements = array(
array(
'type'=>'text,
'value'=>'some value',
'atts'=>null,
),
array(
'type'=>'text,
'value'=>'some other value',
'atts'=>null,
),
);
{{ elements }}
what im trying to do is generate those elements from the macros. they work just fine when called by name:
{{ forms.text(element.0.name,element.0.value,element.0.atts) }}
However what i want to do is something like this:
{% for element in elements %}
{{ forms[element.type](element.name,element.value,element.atts) }}
{% endfor %}
I have tried the following all resulting in the same error:
{{ forms["'"..element.type.."'"](element.name,element.value,element.atts) }}
{{ forms.(element.type)(element.name,element.value,element.atts) }}
{{ forms.{element.type}(element.name,element.value,element.atts) }}
This unfortunately throws the following error:
Fatal error: Uncaught exception 'LogicException' with message 'Attribute "value" does not exist for Node "Twig_Node_Expression_GetAttr".' in Twig\Environment.php on line 541
Any help or advice on a solution or a better schema to use would be very helpful.
I just thought other people may want the answer to this, as provide by fabpot:
This is indeed something that is not supported: calling a macro with a dynamic name (I have added a proper exception to be clearer about the issue).
If you really want to do that, you can do so with the following code:
{{ attribute(forms, element.type, [element.name,element.value,element.atts]) }}
-fabpot
https://github.com/twigphp/Twig/issues/922#issuecomment-11133299
Dynamic macros may not be supported in Twig.
But there is a simple workaround since you can dynamically include other templates.
Example:
Let's say you have a bunch of content modules or content blocks (or however you wanna call them) for your site. And you have Twig macros responsible of rendering each of these modules.
{# modules.twig #}
{% macro module1(config) %}
<div>module one</div>
{% endmacro %}
{% macro module2(config) %}
<div>module two</div>
{% endmacro %}
{% macro module3(config) %}
<div>module three</div>
{% endmacro %}
Now, what you need to dynamically call these macros is to add an extra template for each, like so:
{# module1.twig #}
{% import "modules.twig" as modules %}
{{ modules.module1(config) }}
{# module2.twig #}
{% import "modules.twig" as modules %}
{{ modules.module2(config) }}
{# module3.twig #}
{% import "modules.twig" as modules %}
{{ modules.module3(config) }}
Finally, in your actual page template you just include the template instead of calling the macro.
{# template.twig #}
{# this is the macro's name to be called #}
{% set macro = 'module2' %}
{# this is just a config object to be passed to the macro #}
{% set config = {} %}
{% include macro ~ '.twig' with { config: config } only %}
Et voilá, (dynamically produced) output will be <div>module two</div>.

Resources