Blade templates - Toggle auto escape on the fly? - php

I have a template which calls a number of built in macros which I am including this template from several other places.
Sometimes, I need all of the macros to be called like this:
{{ Form::label('foo', 'Foo') }}
Other times I need them all to be called like this:
{{{ Form::label('foo', 'Foo') }}}
At the moment, I have two separate templates which are identical except for the extra { }, which means I have to edit two files every time I want to change anything.
Is there a way to switch the auto escaping on/off, so that I can use the same file for both situations?
Thanks

No, there's no feature in Laravel that would allow you to do that -- additionally, it'd probably be a bad idea from a code maintenance/security-audit point of view. Looking at a template a few weeks later and not knowing which variables were or were not escaped would be madness.
If you need to do this, "The Right" way would be to extend Blade with your own directive -- something like #escapeIsConfigIsOn and then put your logic for when to escape content in there. The top level function e is what blade uses internally for escaping.
#File: login/vendor/laravel/framework/src/Illuminate/Support/helpers.php
function e($value)
{
return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
}

Related

Can twig macros return values?

I'm trying to write a template in Twig. Inside it I would like it to perform some manipulations on the string data that comes from the controller. In particular, there's a common manipulation (convert from underscore_case to CamelCase) that I'd like to bring out into a separate function. Then later I can use it repeatedly like {% set x = magic(a) ~ magic(b) %}. However I cannot find how to make such a reusable function inside the template itself. There are macros, but those don't seem to be able to return values. Filters are another option that seem to fit the bill, but I can only define those on PHP side.
Can this be done? Or should I do all the advanced string manipulation controller-side? It kinda feels like I'm pulling parts of display logic in there; things that should be in the view.
Twig is for outputting data. If you need to "transform" the data you need to do that before you send it to twig or you need to extend twig
Ideally, all the data you send to twig is just variables and arrays that needs the least amount of manipulation on their own.
When you're actually "in" twig, the data processing can be assumed to be "done" and only needs to be outputted in the appropriate places with minimal logic to decide user interface styles.
So revisit your logic and prepare your data better before sending it to twig.
An example for extending a toolkit class that contains our magic methods to do real wizardry.
class CustomToolkit
{
public function magic_a($a)
{
return strtolower($a); }
public function magic_b($b)
{
return camel_case($b);
}
public function magic_tidle($a, $b)
{
return $this->magic_a($a) ~ $this->magic_b($b);
}
}
Then you add this to your twig instance. I added here a complete instantiation loop. if you have a service provider you can just grab the instance from there and add it to that one.
$twig = new Twig_Environment(new Twig_Loader_Array([
'html' => $contents
]),[
'auto_reload' => true,
'debug' => false,
]);
$twig->addExtension('toolkit', new CustomToolkit ());
echo $twig->render('html', $values);
Then in your twig code you should be able to do something along the lines of
{% set x = toolkit.magic_tidle("value","value_b") %}
You are right, macros do not have return values and you cannot really make them have any. All they do is outputting strings.
Still, you are able to capture string output using set: https://twig.symfony.com/doc/2.x/tags/set.html
The syntax looks similar to this:
{% set var %}
{{ call.macro() }}
{% endset %}
The output of the macro call is then stored inside var. You may want to strip the whitespace though.
But then, consider rethinking what you are doing. Is this still presentation logic, or is your controller simply "too lazy" to transform the strings prior to passing them to twig? If it's really presentation logic, simply adding a twig filter by extending twig surely is worth the hassle. Not only because your code becomes testable.
Can this be done?
Yes! However, Twig templates are not the ideal places to run logic. At least, we should do our best to avoid it. Instead, Controller should return what Twig template needs. Logic should run in Service, Utility, Helper (you name it) etc. and Controller returns it to Twig. Twig then just displays it.
Can twig macros return values?
Yes! Look at this example. It accepts parameters and returns (not a real "return" thing but you get the idea) something back.
Example:
Assuming that the data you are trying to manipulate is something simple.
use Doctrine\Common\Inflector\Inflector;
Controller
{
action() {
$data = Inflector::camelize('hello_world'); // This will become helloWorld
return ....;
}
}
Look into Inflector class. It has useful stuff.

Laravel passing variable out of view partial to main blade, or between partials

I have a Laravel 5.5 app that uses view blades conventionally, nothing fancy. My blade is a long table of data and I split the group sections (item categories) into their own partials to manage the code better.
However, I can't find a way to pass variable data from the partials back to the main blade, or even between partials. The scope of the variables are bound within the partials and I need to show a page total that aggregates the sub-totals from the various partials (sections). I did try using
View::share('portfolio_total', 100);
in the AppServiceProvider, to no avail. I can set a value (like the 100 shown) that stays intact in the master blade, but doesn't pick up the value set in that variable in the partial. Changing the variable value inside the partial doesn't update the value available to the master blade. I've searched extensively for this and there are many examples of passing values into partials, but not back out.
Is there a Laravel trick for defining the equivalent of global variables that can have their value set in an included partial blade, and carry that value to another partial blade, or out to the master blade?
Thanks in advance.
I can't find a way to pass variable data from the partials back to the main blade, or even between partials.
That is because this is absolute bad practice. Hence the framework does not allow that (without abuses).
Why?
In the MVC world (which Laravel implements), views are responsible for displaying data.
If you are computing anything in your views (partials or not), then it means you are giving Views the responsibility of the Controller and/or Model. Which is not good of course.
Then what?
Idea is to compute everything you need before using the views. Two options:
If your view/partial is used only by a single controller method, simply pass the data to the view as usual.
If your view/partial is used in various places, setup a ViewComposer which will be able to pass the computed data each time that partial is used.
What usually I do is the following.
On my controller, to return data I do this:
$data = [];
$data['dummyData'] = Dummy::all();
$data['extraData'] = 'Extra string';
return view('myroute.index', array('data' => $data));
Then on each of my views (The view and the partials)
#php
$dummy = $data['dummyData'];
$extra = $data['extraData'];
#endphp
And then I can use the variable in the blade as any other: {{ $extra }}

Twig add multiple filters?

is it possible to add multiple filters in twig ?
for example i have this single filter
$app->twig->addFilter('_bah',new Twig_Filter_Function('_bah'));
if i want to add all my functions ill do this
$app->twig->addFilter('_bah1',new Twig_Filter_Function('_bah1'));
$app->twig->addFilter('_bah2',new Twig_Filter_Function('_bah2'));
..... etc
if i have many functions i want to use inside Twig template but without calling them by class name like {{ classname.method }} , i want to call them as a filter like {{ "bla bla bla"|trim_me}} is it possible ?
You can create an extension in Twig...
The main motivation for writing an extension is to move often used
code into a reusable class like adding support for
internationalization. An extension can define tags, filters, tests,
operators, global variables, functions, and node visitors.
Creating an extension also makes for a better separation of code that
is executed at compilation time and code needed at runtime. As such,
it makes your code faster.
Most of the time, it is useful to create a single extension for your
project, to host all the specific tags and filters you want to add to
Twig.
http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension
Then you just need one line...
$twig->addExtension(new My_Twig_Extension_Class());

Prevent gettext translation

A variable $status is assigned the string values 'stop' or 'go' (in php action).
In the view, I would like to display the value of $status in such a way that "stop" is displayed in red and "go" in green.
So I wrote something like:
<span class="<?=$status?>"><?= $status?></span>
and defined classes "stop/go" in my css. Everything works fine until I start making it multi-language by wrapping strings in _() in the action. Now gettext not only translates the actually displayed string but also the class name (which I don't want). Is there something like ungettext, so that I can write:
<span class="<?=ungettext($status)?>"><?= $status?></span>
and get the untranslated class name?
I realize that I could just send a bool from the action and use an if statement in the view to create what I want. Alternatively (more ugly) I could add translated class names in the css. But is there a more elegant way?
I think you're over-thinking the problem. Why not simply create another variable in the action, say $status_stye, that you don't apply the translation to? That way you have both the CSS class you want and the translated version of the text.

Smarty caching components

The Smarty FAQ suggests one way to handle cacheable fragments, but it needs each page controller to do tons of work up-front, instead of encapsulating things properly.
We want to get to a stage where we can do something like:
<div id="header">
{our_categories}
{our_subcategories category=$current_category}
</div>
The output of the our_ prefixed functions should be completely cacheable, only relying on the named parameters (if any.) If we referred to {our_categories} in more than one template, they should all refer to the same cached content.
(it's probably worth mentioning that we tried using {insert name="..."} and coding up our own functions, but the results weren't cacheable, and we ended up hand-cranking the HTML retunred, rather than benefiting from Smarty's template processing.)
Our first crack at this uses a custom function smarty_function_our_categories, but the caching's going horribly wrong. Here's what our function looks like:
function smarty_function_our_categories($params, &$smarty) {
$smarty->caching = 2;
$smarty->cache_lifetime = 3600; # 1 hour
if (!$smarty->is_cached(...)) {
// ... do db access to fetch data for template...
$smarty->assign(....);
}
return $smarty->fetch(...);
}
The problem is: calling $smarty->fetch() within a function confuses smarty, making it lose track of which templates have insert-tags, and which don't. The end result is that Smarty forgets to replace certain markers when serving up cached content (markers it puts there to say: "replace this with the results of some non-caching {insert ...} call.) In our case, this manifests itself with our site showing a couple of md5 checksums and a php-serialized memento where our main menu should be - that's not good.
We assume we've made a mistake in how we're building our components, so the question finally becomes:
How do you safely create a caching component using Smarty to render itself?
You should not change caching parameters from inside Smarty function. Wheither or not the result of the plugin output is cacheable is defined when you register plugin.
http://www.smarty.net/manual/en/caching.cacheable.php
To create uncachable content inside cachable template just use {dynamic} blocks like this:
//Registering dynamic non-caching block with Smarty
$template->register_block('dynamic', 'smarty_block_dynamic', false);
function smarty_block_dynamic($param, $content, &$smarty) {
return $content;
}

Categories