Making a custom latex escaper for twig - php

I have a twig template than generates a Latex file. It works fine except that it issues characters escaped for html.
So for instance, the DB returns the string Rosie & Jim.
Twig makes that Rosie & Jim.
For latex I need the & to actually render as \&.
Therefore I think I need a custom escaper for Latex as mentioned here: https://twig.sensiolabs.org/doc/2.x/filters/escape.html#custom-escapers
However, I'm a little green when it comes to twig and can't find any examples of actual escapers anywhere. Does anyone know:
a) Is this what I actually need?
b) What I actually need to do to make one?

Right so I ended up setting it as a service and calling it from the controller as and when it's needed.
Not sure if it's best practice but it works for me.
The service:
<?php
namespace AppBundle\FunBundle\Service;
class Latex
{
public function __construct($twig)
{
$this->twig = $twig;
}
public function escaper()
{
// Get twig env
$twig = $this->twig;
// Start setEscaper called 'latex' - call it what you want
$twig->getExtension('Twig_Extension_Core')->setEscaper('latex',
function($twig, $string, $charset){
// Replace every instance of '&' with '\&'
$string = str_replace("&", "\\&", $string);
}
return $string;
);
}
}
Then in app/config/services.yml:
services:
app.latex:
class: AppBundle\FunBundle\Service\Latex
arguments: ['#twig']
Then in a controller action:
$latexer = $this->get('app.latex');
$latexer->escaper();
And in the twig template itself:
{% autoescape 'latex' %}
# latex/twig goes here
{% end autoescape %}
Ended up writing it up here on my site, which includes a full example of a LaTeX escaper.

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.

Evaluate twig functions in record

I've created a bolt extension which provides a new twig function foo. The twig functions is added to the twig framework with the following code $this->addTwigFunction('foo', 'twigFoo');.
public function twigFoo()
{
$markup = '
<hr>
Foo
<hr>';
return new \Twig_Markup($markup, 'UTF-8');
}
My idea was that the users of the cms can use the twig function in the content types. But when the body of a record is displayed the twig function is visible as plain HTML for example: {{ foo }}
I think the problem is, that the twig template will be rendered before the record body will be assigned. So the body of my record will not be evaluated by twig. Has anyone a idea how to evaluate the twig function witch is use in a record? What's the best practice for this problems?
The field in the ContentType needs allowtwig: true to tell Bolt that you trust the field/editor to allow this, e.g.:
body:
type: html
allowtwig: true
The Problem is that Twig does not render Twig inside a Twig variable. You could create an escape function to still do that. Anyway this might not be the best idea to give your CMS users the possibility to use Twig as this gives them full access to your code.
Anyway, here is an escape function that could help you
$this->app['twig']->getExtension('core')->setEscaper('code', function($twigEnv, $string, $charset) {
$twig = clone $this->app['twig'];
$twig->setLoader(new \Twig_Loader_String());
return $twig->render($string);
});
You could then use the twig filter "code" in your template. e.g.:
{{ record.body|escape('code') }}

Symfony2 Translation Add Html

I want to add some HTML to Symfony 2 Translation. This way I will know which phrases in my application are translated and which not.
I found in "Symfony\Component\Translation\Translator.php" function "trans". Now i want to add something in function return, for example "< /br>":
/**
* {#inheritdoc}
*
* #api
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
if (null === $locale) {
$locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
}
if (null === $domain) {
$domain = 'messages';
}
if (!isset($this->catalogues[$locale])) {
$this->loadCatalogue($locale);
}
return strtr($this->catalogues[$locale]->get((string) $id, $domain)."</br>", $parameters);
}
The issue is that when I run my application I'm getting for example "Tag< / b r>" (I have add spaces because in normal way it doesn't show here. HTML doesn't interprate this as HTML code but as a string. Is there any way to achieve what I want ? Maybe it is but in the other way ?
This happens because you have the Twig Escaper extension active. That extension adds automatic output escaping to Twig, it defines the autoescape tag and the raw filter.
So I think the best option you got here is to define a new twig extension to let you translate your html strings without having to repeat myvar|raw each time.
To see how it is possible to create a new Twig extension please check the docs here.
Use the same extension when escaping for JS and there should be no need to use anything else especially in your PHP controllers. That's because the escaping is done at the Twig level. Just remember to declare your new Twig filter as safe to avoid automatic escaping again:
$filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
If you need to do some extra processing with the requested data so that you can track what strings are being requested and what not then just declare a new service as a proxy to the Symfony translation one. Your Twig extension can use the same service. This way you can converge all the requests to one single service.
Here a few useful links for you:
http://twig.sensiolabs.org/doc/api.html#escaper-extension
http://twig.sensiolabs.org/doc/advanced.html#automatic-escaping
I'll suggest you simply to use Markdown in your translation.
Then you can parse your translated message with a Markdown parser.
Example in Twig: 'my.message'|trans|markdown (I suppose you have a Markdown filter, there is KnpMarkdownBundle)
MY SOLUTION:
I have achieved what I wanted in few steps:
Override Translator.php and change translator.class in parameters.yml
public function trans($id, $parameters, $domain, $locale)
{
$return = parent::trans($id, $parameters, $domain, $locale);
return "".$return.'');
}
Set translation class in your css.
Set autoescape option to false in parameters.yml
twig:
autoescape: false

Does Twig support this?

I'm playing around with a CMS idea for Symfony and I'm not sure if what I want to implement is possible. I'm looking for some guidance.
I want to create a twig function that acts more like a block.
I want to be able to write template like this:
{% content('main_content') %}
<p>Some markup here</p>
{% end content('main_content') %}
where content($id) is a function that gives me access to the markup inside and allows my twig extension to change that markup if needed.
The content($id) function's goal will be to change the mark up based on what $id is given.
Obviously my train of thought on this implementation is off because I cannot find any docs relevant to this.
I'm not sure how to word this question : Is it possible to write a twig function that acts like a block and gives me access to the inner data. If possible is there some examples or blogs you can link me to? I'm using Symfony so answers with Symfony implementations are a plus.
edit: an example of the content($id) function
class TwigExtension extends \Twig_Extension{
public function content ($id, $content) {
/* pseudo code now*/
if($id = "some condition"){
return $someNewContent;
}
return $content;
}
}
You need to create a new Twig tag. More documentation here: https://github.com/twigphp/Twig/blob/master/doc/advanced.rst#tags
Also the Twig_Node_Block will give a good idea of what you need to build:
https://github.com/twigphp/Twig/blob/1.x/lib/Twig/Node/Block.php

How to render content from string/database with twig? [duplicate]

This question already has answers here:
What to use instead of Twig_Loader_String
(7 answers)
Closed 1 year ago.
Because of several reasons including translation of content I had to build a simple CMS to render the pages of my Symfony2 application.
My problem is that is seems impossible to render content from a string. Twig only accepts files. My content may contain dynamic parts like locale or similar, so the render power of twig would be very useful.
I tried to render it using the TwibstringBundle, but its functionality is quite limited and it does not work with the path-function.
see http://twig.sensiolabs.org/doc/functions/template_from_string.html
and http://symfony.com/doc/current/cookbook/templating/twig_extension.html#register-an-extension-as-a-service
{% include template_from_string("Hello {{ name }}") %}
{% include template_from_string(page.template) %}
Since the string loader is not loaded by default, you need to add it to your config.
# src/Acme/DemoBundle/Resources/config/services.yml
acme.twig.extension.loader:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
Where Acme/acme is your application name and DemoBundle is the bundle you want to enable it for.
Symfony 2.7 makes this easy from PHP, too:
$twig = $this->get('twig');
$template = twig_template_from_string($twig, 'Hello, {{ name }}');
$output = $template->render(['name' => 'Bob']));
twig_template_from_string function's source code is as follows:
function twig_template_from_string(Twig_Environment $env, $template)
{
return $env->createTemplate($template);
}
It means that if you already have a twig environment then it's better to call directly:
$template = $env->createTemplate($templateString);
$parsedContent = $template->render(array('a'=>'b'));

Categories