disable escaping/urlencoding in Twig_SimpleFunction - php

i know it's one of the common and frequently asked questions about twig, but i get stuck and confused
Problem: Function output will be escaped and instead of /?some_var=value&maybe_another_var=another_value I get %2F%3Fsome_var%3Dvalue%26maybe_another_var%3Danother_value which completelly ruins my anchor-tags
Goal: I'm trying to prevent auto_urlencoding which happens all the time with my custom function.
What I use: a utilphp::util::add_query_arg() method and a custom Twig-Function (Twig_SimpleFunction)
How I use it: in the code
$twig->addFunction( new \Twig_SimpleFunction('util_add_query_args',
function(){
return \utilphp\util::add_query_arg( func_get_args() );
},
array('is_safe' => array('all')) //also tried array('html') but didn't worked
));
How I use it: in the tempalte
<img src="//{{app_host_name}}/{{ other_url_segments}}{{ util_add_query_args( {'fontStyle' : style } , data.query_string )}}">
this should append &fontStyle=value_from_variable to the given query string and return it as is
data.query_string — may already contain a querysting like ?some_var=value&maybe_another_var=another_value but it can be different from one request to another. In some cases it's empty, so in order to get a valid query sting i have to use util::add_query_arg() since it also covers such cases
Didn't work:
adding |raw to {{ util_add_query_args( {'fontStyle' : style } , data.query_string ) }}
adding
{% autoescape false %}
<img src="//{{app_host_name}}/{{ other_url_segments}}{{ util_add_query_args( {'fontStyle' : style } , data.query_string )}}">
{% endautoescape %}
What do I missing?! How to solve this problem?

Alright, it seems like func_get_args() was "the bad guy". Do not know why exactly, but since I changed
function(){
return \utilphp\util::add_query_arg( func_get_args() );
}
to
function($args_arr, $query_string){
return \utilphp\util::add_query_arg( $args_arr, $query_string );
}
it works... and no {% autoescape false %} or |raw needed too.

Related

Alternative to template_from_string for processing strings with calls to custom Twig functions

Is there an alternative to using template_from_string() for evaluating strings that contain calls to custom Twig functions (or any other complex Twig code for that matter)?
This is a simplified version of the code I have right now.
{% set content = {
'item_1' : {
'images' : {
'src': 'assets/images/img_1-600.jpg',
'srcset': "{{ assets('assets/images/img_1-600.jpg') }} 600w, {{ assets('assets/images/img_1-1200.jpg') }} 1200w",
}
}
# additional items follow
}
{% for data in content %}
<img src="{{ data.images.src }}" srcset="{{ include(template_from_string(data.images.srcset)) }}" alt="">
{% endfor %}
The assets() function simply returns a revisioned version of the static asset for the given path (i.e. assets('assets/images/img_1-600.jpg) renders as something like 'assets/images/img_1-600-a4c2254a6d.jpg).
The problems start with img srcset attribute which can become pretty complex and usually contains references to multiple assets that need to return revisioned values for static assets.
Now I know that I could modify the assets() function to support that kind of complex scenario but I'd like to keep things simple and let assets() only handle 1:1 transformations.
The only way to achieve this by using functionality provided by Twig seems to be template_from_string() combined with include, which in itself is not terrible but it does kind of look bulky for a simple operation as evaluating and rendering a string.
Not to mention that template_from_string requires StringLoaderExtension() to be loaded by default, which again I'd like to avoid using for performance reasons.
While the concatenation approach is not a bad idea, after much consideration I came to the conclusion that this is one of those places where having a custom Twig function makes much more sense.
The main argument in favor of using a function compared to simple concatenation is that with a function there is no need to worry about properly formating the template code (i.e. forget a space between the asset and the size descriptor in the srcset attribute value).
This approach also completely eliminates any need to use template_from_string() or additional extension dependencies.
Here is the final solution.
The template code, plain and simple, with plenty of overview.
{% set content = {
'item_1' : {
'images' : {
'src': 'assets/images/img-600.jpg',
'srcset': srcset([
[ assets('assets/images/img-600.jpg'), '600w' ],
[ assets('assets/images/img-800.jpg'), '800w' ],
[ assets('assets/images/img-1000.jpg'), '1000w' ],
[ assets('assets/images/img-1200.jpg'), '1200w' ]
]),
}
}
# additional items follow
}
{% for data in content %}
<img src="{{ data.images.src }}" srcset="{{ data.images.srcset }}" alt="">
{% endfor %}
The actual Twig function called srcset, that is used to generate a value for the srcset attribute from provided data in the above template.
public function srcset(array $srcset)
{
$output = [];
foreach ($srcset as $set) {
// Both parameters need to exist or the set isn't added to the output...
if (!(isset($set[0]) && isset($set[1]))) {
// ... just some exception handling that isn't of relevance for the question
// ...
continue;
}
$output[] = sprintf('%s %s', $set[0], $set[1]);
}
return implode(', ', $output);
}
To avoid any extra plugins, you could just concat the variables
{% set content = {
'item_1' : {
'images' : {
'src': 'assets/images/img_1-600.jpg',
'srcset': assets('assets/images/img_1-600.jpg')~' 600w,'~assets('assets/images/img_1-1200.jpg')~' 1200w',
}
}
} %}

timber/twig how to pass `posts` php-object to JavaScript? Some values are lost

When I'm trying to pass the information contained in {{posts}} I cannot retrieve all of it, at least not the post.link information
{% for post in posts %}
<script>
var t = JSON.parse('{{post|json_encode(constant('JSON_HEX_APOS'))|e('js')}}')
t.link = '{{post.link}}'
console.log(t)
</script>
{% endfor %}
Without manually adding the link, it doesn't show up
Why is this happening and how could I workaround this?
EDIT: related https://github.com/timber/timber/issues/1434
You shouldn’t encode your whole post object. You should encode all the values you need separately.
The link for your post doesn’t show up, because link is not a property, but a method of the Timber\Post object. This might be a little bit confusing, because in Twig we use {{ post.link }}. It looks like it’s a property or maybe an array item. But we could also use {{ post.link() }}, which is the same as {{ post.link }}. You can read more about this in the Variables section in Twig for Template Designers.
So what I would do is build a new array with the data you need and encode it to JSON in PHP with wp_json_encode().
PHP
$posts_json = [];
foreach ( $posts as $post ) {
$posts_json[] = wp_json_encode( [
'title' => $post->title(),
'link' => $post->link(),
] );
}
$context['posts_json'] = $posts_json;
By only adding the data you need, you keep the output in the frontend small. Otherwise, you would end up with a lot of data that you will never and that only increases the page size unnecessarily.
And then in Twig, you could do it like this:
{% for post in posts_json %}
<script>
var t = {{ post }}
console.log(t)
</script>
{% endfor %}

ezplatform render links with url and object name from multi-relational content item in content type

does anyone know now to create a custom view type for ez platform? The default 3 have been exhausted and we need a new one for 'link'
Alternatively, does anyone know how to use the render( controller( with a custom template as this would also resolve out block right now.
Basically, we have a multi-relational field in a content object used and we need to print links to all the related contentIds, path works great but we cannot find a way to extract the name of the content object for the link without doing some fairly funky tpl logic of passing in params.
EG: As a hack for now we can pass in "embed_type" as a custom param with the render(controller("ez_content:viewAction" to pull in an alternate view for the content object for a specific content type and view type.
{% if embed_type is defined %}
{% include "embed/#{embed_type}.html.twig" %}
{% else %}
<h1>{{ ez_field_value( content, 'name') }}</h1>
{% endif %}
However, this is very ugly and all we really want to do is use 1 template for all content types, so all we need to do is loop through the relational field and print links (as the only thing available in the content field: "destination ids"). I am sure there used to be this option in the docs but i cannot find it anymore eg:
{% set links = ez_field_value( footer, "first_links_row" ).destinationContentIds%}
{% for id in links %}
{{ render(controller("ez_content:viewAction", {"contentId": id, "template": "link.html.twig"})) }}
{% endfor %}
Where the link.html.twig would simple print the link:
<a href="{{ path( "ez_urlalias", {"contentId": id} ) }}">
{{ ez_field_value( content, "name" ) }}
</a>
If using a custom tpl is not possible with the render (controller ( helper then a new custom view type would also fix this issue, but i cannot find documentation for either.
You can create a twig function that would do that. We have something like this:
Definition:
new Twig_SimpleFunction(
'content_name',
array($this, 'getContentName')
),
Implementation:
public function getContentName($content, $forcedLanguage = null)
{
if (!$content instanceof Content && !$content instanceof ContentInfo) {
$contentInfo = $this->repository->getContentService()->loadContentInfo($content);
} elseif ($content instanceof Content) {
$contentInfo = $content->contentInfo;
} else {
$contentInfo = $content;
}
return $this->translationHelper->getTranslatedContentNameByContentInfo($contentInfo, $forcedLanguage);
}
which enables you to provide either content id, content info or content itself, and it returns translated content name

Twig extension filter or function name in twig variable does not work?

{{ row[col.key] | col.filter }}
I have filter in a col.filter variable. I tried creating eval filter like mentioned in following link, https://github.com/fabpot/Twig-extensions/pull/53/files and also tried https://github.com/marcj/twig-apply_filter-bundle or calling like
{{ row[col.key] | eval(col.filter) }}
or
{{ eval(row[col.key] , col.filter) }}
But it doesn't work.
Demo for the issue is here,
https://github.com/vishalmelmatti/twig_dynamic_filter
Ok, this is a bit hairy, but how about using a template_from_string + include approach? Somewhat ugly, and probably quite expensive, but at least it should work.
Proof of concept:
{% set col, row = { key: 'x', filter: 'raw' }, { x: '<b>one</b>', y: '<b>two</b>' } %}
{{ include(template_from_string('{{ row[col.key]|' ~ col.filter ~ ' }}')) }}
...which will output: one
While this...
{% set col, row = { key: 'x', filter: 'escape' }, { x: '<b>one</b>', y: '<b>two</b>' } %}
...
...will output: <b>one</b>
You can't do this at this point, but I suggest you to create your own twig method or filter that takes the filter you want to apply as an argument.
I suggest you to use or get inspiration from this bundle : https://github.com/marcj/twig-apply_filter-bundle
As stated on the IRC, you could go for another approach, and use a Format interface to apply formating to your elements:
Here you have a simple POC https://gist.github.com/Lumbendil/d94109e91ceab7fae7e7
Here's the solution:
https://github.com/marcj/twig-apply_filter-bundle
It's a Symfony bundle that provides exactly what you need. You can use it without symfony as well.

twig: create custom tag that calls a functions

SETUP:
Twig 1.13.1
PHP 5.4.3
PROBLEM:
I am needing help setting up a custom tag that calls a function that i have already built...
Current Code:
Template Code
{% set stories = get_latest_stories(2, sports) %}
{% for story in stories %}
{{ story.headline }} <br>
{% endfor %}
Controller
$function = new Twig_SimpleFunction('getViewStories', function (section, limit) {
return news_stories::getStories(section,limit);
});
$twig->addFunction($function);
$twig->render("storyList.html");
GOAL:
No with that said I would like to use a custom tag like
{% get_latest_stories 2 sports %}
to call the same function as above. The new way looks nicer and is easier to follow
Why not fetch your stories in the controller instead of the template? This does not seem like a job for the view layer...
So, something like this:
$twig->render("storyList.html", array(
'stories' => news_stories::getStories($section, $limit)
));
Then, you'll have a stories variable available in your template.
here is simple example how to write twig extension
Following code is taken from my unfinished project
function file_import($value){
//some code here
return $value;
}
$app['twig']->addFunction('file_import', new Twig_Function_Function('file_import'));
usage
{{ file_import('value') }}

Categories