Can an object tell Twig not to escape output when it is {{ printed }} - twig

I've got a situation where some variables sent to my Twig templates are plain old variables, so I want them to be html-escaped (as is the default behaviour). But other variables sent to my templates are really objects with __toString() renderers... and some of these objects send out raw HTML (e.g. from a WYSIWYG editor like TinyMCE or CKEditor).
Ideally I'd like for my template designers to not have to use the |raw filter on the objects, but instead somehow have the objects tell Twig that they're already escaped.
In other words, I'm trying to mimc the behavior of a Twig function that sets is_safe, but without requiring template designers to use a function.
E.g. I could write a Twig function using the is_safe parameter in its definition and be able to have this in my templates:
{{ figure_out_what_to_do(something) }}
(where the figure_out_what_to_do knows to inspect the "something" object to ascertain whether or not it needs to be escaped). BUT to me this is no better than having to remember to put |raw after every output of "something". So instead I'd like to be able to do this:
{{ something }}
...and have Twig recognize that something is an object and hence ask it whether or not it needs to be escaped.
I'm guessing the answer is "no", but figured I'd ask in case someone who knows more about Twig internals has any pointers for me.
Thanks.

In the __toString() method you could instead of return the html output, do this : return new Twig_Markup($html, 'UTF-8'); thus marking it as a safe and not to be escaped

Instead of returning a new \Twig_Markup object in __toString() (which causes a fatal error since it must return a string) you could extend \Twig_Markup:
class Something extends \Twig_Markup {
public function __toString() {
return $this->safeValue;
}
public function count() {
return mb_strlen($this->safeValue);
}
}
Twig looks at the object to see if it is an instance of \Twig_Markup when deciding to escape the string or not. Here’s the source for \Twig_Markup:
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Marks a content as safe.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
class Markup implements \Countable
{
protected $content;
protected $charset;
public function __construct($content, $charset)
{
$this->content = (string) $content;
$this->charset = $charset;
}
public function __toString()
{
return $this->content;
}
public function count()
{
return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content);
}
}
class_alias('Twig\Markup', 'Twig_Markup');
As you can see in the source code, \Twig_Markup implements \Countable. That’s why I’ve overridden the implementation of public function count() in my example.

Related

PHP __LINE__ equivalent for twig

There is a way to get PHP __LINE__ equivalent for Twig?
It's almost impossible to search __LINE__ on Google as exact word...
The purpose is purely for debugging a long twig file containing nested twig code inside a complex Js code, to console.log it.
The version of twig I'm using is 2.12.5.
Thanks
First things first:
As seen in the PHP documentation the (magic) constant will return the current line number of the file.The "problem" here is that twig converts templates to PHP in order to render them.
This means if you were actually were to be able to use __LINE__ inside a template, it would report back the line number from either a temporary PHP file or a cached PHP file, wether you have caching enabled or not.
TLDR: Using __LINE__ inside a template is going to report back "false"/useless information.
However, you can easily extend twig and even introduce new tags which you then can use inside templates. These customs tags allow you to modify/alter the compilation of the template's PHP file.
The interesting part here is that the "compiler" is able to provide your custom tag on which specific line the tag was called.
We can even create a custom tag, register it with twig and let the tag report back the line number in the parsed template.
Step 1 - Create a TokenParser
The TokenParser is responsible for parsing the template and allows you to choose a name for your tag. The code below will be responsible to create a simple, empty tag named line
<?php
namespace MyProject\Base\Twig\TokenParser;
use \MyProject\Base\Twig\Node\Line as LineNode;
class Line extends \Twig_TokenParser {
public function parse(\Twig_Token $token)
{
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new LineNode(new \Twig_Node(), $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'line';
}
}
Step 2 - Create a Node
The Node is responsible for converting the template code to actual PHP code
<?php
namespace MyProject\Base\Twig\Node;
class Line extends \Twig_Node {
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null) {
parent::__construct(['body' => $body,], array(), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler) {
$compiler->write('echo '.$this->getLine().';')
->write(PHP_EOL);
}
}
Step 3 - Register the tag with twig
<?php
namespace MyProject\Base\Twig;
class MyProjectTwigExtension extends Twig_Extension {
public function getTokenParsers() {
return [
new \CMS4U\Base\Twig\TokenParser\Line(),
];
}
public function getName() {
return 'MyProjectTwigExtension';
}
}
<?php
$twig->addExtension(new \MyProject\Base\Twig\MyProjectTwigExtension());
If everything is good, you can now use the custom tag {% line %} wherever you like in any template
Foo
Bar
FooBar
Current line number is {% line %} {# 4 #}

How to use php variable instead .twig file? [duplicate]

I'm working on application written in symfony2 and I want to send email after some action/event... the problem is, that the users can define something like "email templates" which are stores in db like simple string, for example: "This is some email from {{ user }}" and I need to render body for that email which should use that template...
In symfony documentation from this link: https://symfony.com/doc/2.0/cookbook/email/email.html#sending-emails the method for render view is $this->renderView and it expects the path to file such as "bundle:controller:file.html.twig", but my template is simple string from database...
How can I render it?
Twig_Loader_String is deprecated and was always designed for internal use anyway. The usage of this loader is strongly discouraged.
From the API doc:
This loader should NEVER be used. It only exists for Twig internal
purposes. When using this loader with a cache mechanism, you should
know that a new cache key is generated each time a template content
"changes" (the cache key being the source code of the template). If
you don't want to see your cache grows out of control, you need to
take care of clearing the old cache file by yourself.
Also check out this issue: https://github.com/symfony/symfony/issues/10865
The best way I know to load a template from a String source are:
From a controller:
$template = $this->get('twig')->createTemplate('Hello {{ name }}');
$template->render(array('name'=>'World'));
as described here: http://twig.sensiolabs.org/doc/recipes.html#loading-a-template-from-a-string
From a twig template:
{{ include(template_from_string("Hello {{ name }}", {'name' : 'Peter'})) }}
as described here: http://twig.sensiolabs.org/doc/functions/template_from_string.html
Note, that the 'template_from_string' - function is not available by default and needs to be loaded. In symfony you would do this by adding a new service:
# services.yml
services:
appbundle.twig.extension.string:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
This should work. Replace "Hello {{ name }}" with your template text, and fill the array that is passed into the render function with any variables that you need.
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
Here's a solution that works with Symfony 4 (and possibly older versions as well, although I haven't tested it) and allows you to work with templates stored in the database the same way you would work with templates in the filesystem.
This answer assumes you're using Doctrine, but is relatively easy to adapt if you're using another database library.
Create the Template entity
This is an example class that uses annotations, but you can use whatever configuration method you're already using.
src/Entity/Template.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="templates")
* #ORM\Entity
*/
class Template
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", nullable=false)
*/
private $filename;
/**
* #var string
*
* #ORM\Column(type="text", nullable=false)
*/
private $source;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", nullable=false)
*/
private $last_updated;
}
The bare minimum fields are filename and source, but it's a very good idea to include last_updated or you'll lose the benefits of caching.
Create a DatabaseLoader class
src/Twig/Loader/DatabaseLoader.php
<?php
namespace App\Twig\Loader;
use App\Entity\Template;
use Doctrine\ORM\EntityManagerInterface;
use Twig_Error_Loader;
use Twig_LoaderInterface;
use Twig_Source;
class DatabaseLoader implements Twig_LoaderInterface
{
protected $repo;
public function __construct(EntityManagerInterface $em)
{
$this->repo = $em->getRepository(Template::class);
}
public function getSourceContext($name)
{
if (false === $template = $this->getTemplate($name)) {
throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
}
return new Twig_Source($template->getSource(), $name);
}
public function exists($name)
{
return (bool)$this->getTemplate($name);
}
public function getCacheKey($name)
{
return $name;
}
public function isFresh($name, $time)
{
if (false === $template = $this->getTemplate($name)) {
return false;
}
return $template->getLastUpdated()->getTimestamp() <= $time;
}
/**
* #param $name
* #return Template|null
*/
protected function getTemplate($name)
{
return $this->repo->findOneBy(['filename' => $name]);
}
}
The class is relatively simple. getTemplate looks up the template filename from the database, and the rest of the methods use getTemplate to implement the interface that Twig needs.
Add the DatabaseLoader to your service config
config/services.yaml
services:
App\Twig\Loader\DatabaseLoader:
tags:
- { name: twig.loader }
Now you can use your database templates in the same way as filesystem templates.
Rendering from a controller:
return $this->render('home.html.twig');
Including from another Twig template (which can be in the database or filesystem):
{{ include('welcome.html.twig') }}
Rendering to a string (where $twig is an instance of Twig\Environment)
$html = $twig->render('email.html.twig')
In each of these cases, Twig will check the database first. If getTemplate in your DatabaseLoader returns null, Twig will then check the filesystem. If the template isn't available in the database or the filesystem, Twig will throw a Twig_Error_Loader.
Clone the native twig service and replace the filesystem loader with the native twig string loader:
<service id="my.twigstring" class="%twig.class%">
<argument type="service" id="my.twigstring.loader" />
<argument>%twig.options%</argument>
</service>
<service id="my.twigstring.loader" class="Twig_Loader_String"></service>
Usage example from within a controller:
$this->get('my.twigstring')->render('Hello {{ name }}', array('name' => 'Fabien'));
The best way to do it is to use template_from_string twig function.
{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}
See documentation of template_from_string
See why it is not a good idea to use Twig_Loader_Chain or Twig_Loader_String for that purpose on this github issue by stof.
As of Twig 1.10, the Twig Engine doesn't support rendering strings. But there is a bundle available which adds this behavior called TwigstringBundle.
It adds the $this->get('twigstring') service wich you can use to render your strings.
(As September '19, the current version of Twig is 2.X, and version 3 is around the corner; so this is only applies to very old versions of Twig).
This work for me:
$loader = new \Twig\Loader\ArrayLoader([
'Temp_File.html' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);
echo $twig->render('Temp_File.html', ['name' => 'Fabien']);
https://twig.symfony.com/doc/2.x/api.html
FYI, This feature was suggested to be added in the core of Twig as of 1.11.0, but will be needed to be activated by the developper.
With Symfony 2.2 you can use the Twig_Chain_Loader
How to register another (custom) Twig loader in Symfony2 environment?
I recently had to implement a CMS used by multiple parties where each party could completely customize their templates. To achieve this I implemented a custom Twig Loader.
The most difficult part was coming up with a naming convention for the templates guaranteed not to overlap with any existing templates, for example <organisation_slug>!AppBundle:template.html.twig.
In case the template was not customised, the template AppBundle:template.html.twig would have to be loaded as fallback template.
However, this is not possible with the Chain Loader (AFAIK) because there the template name cannot be modified. Therefore I had to inject the default loader (i.e. the loader chain) into my loader and use it to load the fallback template.
Another solution would be to pass the request stack or the session to the template loader, making it possible to automatically detect the organisation, but this is difficult because the security component depends on the templating subsystem, causing circular dependency issues.
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('recipient#example.com')
->setBody('hai its a sample mail')
;
$this->get('mailer')->send($message);

Find a div in a block render in Symfony

I am doing a site with symfony 4. I start on this framework and I have a problem of design on my site.
I've been looking for where it might come from, but I can not find the code I'd like to remove.
The part of the code I would like to remove is the following because the result is pretty ugly:
I can not find this in my code:
in my controller :
/**
* #return Response
*/
public function newLetterAction(Request $request): Response
{
$form = $this->createForm(CustomerNewsletterType::class, new Customer());
$form->handleRequest($request);
$facebook = $this->manager->getRepository(ExternalUrl::class)->findOneByCode('facebook');
$instagram = $this->manager->getRepository(ExternalUrl::class)->findOneByCode('instagram');
return $this->templatingEngine->renderResponse('#SyliusShop/Homepage/_newsletter.html.twig', [
'facebook' => $facebook,
'instagram' => $instagram,
'form' => $form->createView(),
'rova_refonte' => (in_array($this->container->get('request_stack')->getMasterRequest()->attributes->get('_route'),["sylius_shop_homepage"]) ? true : false)
]);
}
in my formType :
class CustomerNewsletterType extends AbstractResourceType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class, [
'label' => 'app.ui.newsletter',
'attr' => [
'placeholder' => 'app.ui.email'
]
])
;
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix(): string
{
return 'app_customer_newsletter';
}
}
in my twig:
{{ render(controller('app.controller.shop_homepage:newLetterAction')) }}
if anyone could tell me how to find the code, it would help me a lot.
Thank you
Everything is done under the hood when you call $form->createView().
To sum up, every type of field into the form has a base rendering using twig blocks (so does the form itself), which can be overriden. This is what is called the form theme, there's a base one which is usually this one in the twig-bridge.
You can create new themes, extend the existing one, or event create just what you need for a specific form (hint : the getBlockPrefix function in your form type is used for this).
You can find all the documentation about the form rendering here :
https://symfony.com/doc/current/form/form_customization.html
Most functions described in this documentation are in fact calling twig blocks of form themes, and you can find the documentation about this here :
https://symfony.com/doc/current/form/form_themes.html
Keep in mind: Removing such a class / div might break existing CSS, error rendering or everything done in javascript targeting this class.
Most likely you use a bootstrap 3/4 form theme and there works standard form_row template layout.
To customize current form/another form elements, use "How to Work with Form Themes" tutorial.

Can someone explain the syntax in the geb Page content DSL?

I have problems to understand the syntax with which pages are described (defined) in Geb. The following is an example page class that works fine but I don't understand how it works.
import geb.Page
class GebHomePage extends Page{
static url = "http://gebish.org"
static at = { title == "Geb - Very Groovy Browser Automation" }
static content = {
seeGuideButton { $('div', class:'ui huge primary button') }
uiHeader { $('h1', class:'ui header', 0)}
}
}
I am interested in the syntax inside the closure referred to by content. Geb describes the syntax for the content DSL as
«name»(«options map») { «definition» }
I don't understand this syntax. The name part looks like a variable, but it is not declared (I miss a def or something similar) and the last part looks like a closure. But where is the connection between the name and the closure. There is no equal sign connecting the two. In a standard groovy class the syntax
«name» { «definition» }
does not compile. What happens in one line of the content DSL? What precisely are uiHeader and seeGuideButton and how are they related to the following closures? How does this syntax work exactly?
Behind the scenes, Geb looks for a method with the signature:
uiHeader(Map, Closure)
In groovy, you can catch when a method is missing (see methodMissing) so internally Geb will catch this and know how to resolve any future requests for that variable
Also in groovy, a Closure as the last parameter can be outside the brackets, which is why you can do
uiHeader(required: false) { $('h1') }

renderArgs is null in template in play! framework

I have a controller function like this
public static void renderCustomersList() {
System.out.println("renderArgs"+renderArgs.toString());
render(renderArgs);
}
In my groovy template
renderArgs ${renderArgs.toString()}
I have renderArgs with value printed in my console but when i pass to the template its printing as null. I wanted those values in my main.html. I printed renderArgs in Both my renderCustomersList.html and main.html the values were null on both templates. What could be the error.
You do not need to pass renderArgs as a parameter to the render function - all the variables in the renderArgs scope will be available in the template anyway. But instead of accessing the renderArgs object itself in the template, go for the independent values instead - see below.
Controller:
public static void renderCustomersList() {
... // some code to initialize yourVariable
renderArgs.put("yourVariable", yourVariable);
render(); // renderArgs not needed as a parameter
}
And in the template:
${yourVariable}
Note also that you don't have to user renderArgs if you don't want to. You can also pass some parameters to render() like this:
public static void renderCustomersList() {
...
render(customers, products, foobar);
}
and then you can get at those parameters in your template with the variable names:
#{list items:customers, as:cust}
...
Foobar: ${foobar}
The only case in which you need to use renderArgs is if you want to use different names for your parameters in the template than in the controller. This is potentially confusing though, so I try not to do it.

Resources