I am changing my assets from PNGs to SVGs, where appropriate, however I am having difficultly including these SVGs in my Twig templates.
I am trying to include the SVG like this:
{{ source('/assets/img/crmpicco-horizontal-logo.svg') }}
However, this results in the following error:
Unable to find template "/assets/img/crmpicco-horizontal-logo.svg"
(looked into:
/Library/WebServer/Documents/crmpicco/symfony/app/Resources/views,
/Library/WebServer/Documents/crmpicco/symfony/vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form).
Why can't I include this SVG in the same directory as my other assets? Specifically, i'm interested in finding out why a SVG can't be treated as an asset with Assetic.
Here is the solution I use (Twig with Symfony 4 and Webpack Encore) :
Inside your twig config, declare your public path :
twig:
paths:
"%kernel.project_dir%/public": public_path
Load your svg asset like that :
{{ source('#public_path'~asset('build/images/your_file.svg')) }}
You have to rename your svg file with a twig extension crmpicco-horizontal-logo.svg.twig and do this :
{% include ":svg:crmpicco-horizontal-logo.svg.twig" %}
The folder svg is in app/Ressources/views in this example
The #adrien-lamotte solution was working for inlining svg tag. However I also needed to dynamically add class(es) on the tag.
I ended up coding a specialized Twig extension for loading SVG and adding attributes, so you do:
{{ source(svg_asset('/images/front/connexion.svg'))|add_class('svg-picto') }}
The svg_asset function is just to limit boilerplate; whereas the add_class and add_html_attr filters allows for the SVG DOM manipulation.
The extension class:
<?php
namespace App\Twig\Extension;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Twig\TwigFilter;
class SvgExtension extends AbstractExtension
{
private $assetExt;
public function __construct(
\Symfony\Bridge\Twig\Extension\AssetExtension $assetExt
) {
$this->assetExt = $assetExt;
}
public function getFunctions(): array
{
// We could use a macro also, but this would need to be imported
// each time.
// https://twig.symfony.com/doc/3.x/tags/macro.html
return array(
new TwigFunction('svg_asset', [$this, 'svg_asset_url']),
);
}
public function getFilters(): array
{
return array(
new TwigFilter('add_html_attr', array($this, 'add_html_attr'), [
'is_safe' => ['html'],
]),
new TwigFilter('add_class', array($this, 'add_class'), [
'is_safe' => ['html'],
]),
);
}
public function svg_asset_url($path): string
{
$assetUrl = $this->assetExt->getAssetUrl($path);
return '#public_path/' . $assetUrl;
}
public function add_html_attr(string $html, $attr_name, $attr_value): string
{
// We assume a top level tag, on which will add an attribute
// value (without checking for a prior existence of the attribute key).
// We could use a naive Regex or a DOM library.
// https://github.com/scotteh/php-dom-wrapper#attr
// Or just the plain PHP DOMDocument() https://www.php.net/domdocument.loadhtml.
$doc = new \DOMWrap\Document();
$doc->setLibxmlOptions(LIBXML_HTML_NOIMPLIED);
return $doc
->html($html)
->children()->first()
->attr($attr_name, $attr_value);
}
public function add_class(string $html, $class_value): string
{
return $this->add_html_attr($html, 'class', $class_value);
}
public function getName()
{
return 'svg.extension';
}
}
#public_path is declared in config/packages/twig.yaml:
twig:
paths:
"%kernel.project_dir%/public": public_path
It is important to note you can have a near equivalent solution to target the SVG with CSS, without the overhead: just add an HTML tag wrapper. For eg.
<div class="svg-wrapper">
{{ source('#public_path'~asset('build/images/your_file.svg')) }}
</div>
Related
I'm using Twig's markdown_to_html filter, and it works very well.
However, in some use cases, I'd want it to generate HTML, but without the paragraph tags.
For instance, from this Markdown content:
Hello, this is **some Markdown**
I want the exported HTML to be:
Hello, this is <strong>some Markdown</strong>
But the result is currently:
<p>Hello, this is <strong>some Markdown</strong></p>
I looked into the filter's source and didn't se any option to do so.
Is there a way to do this, or should I create my own Twig filter?
I'd prefer to avoid the striptags filter if possible, because I don't want to list all the tags I'll allow (unless there is a reverse striptags where you can specify the tags you want removed ?)
It looks like you're using league/commonmark which has an "Inlines Only" extension for this exact purpose! It will avoid outputting block-level elements like paragraphs, headers, etc. - only things like emphasis and links would be rendered as HTML.
To use it, construct your Markdown converter like this:
<?php
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
use League\CommonMark\MarkdownConverter;
// Define your configuration, if needed
$config = [];
// Create a new, empty environment
$environment = new Environment($config);
// Add this extension
$environment->addExtension(new InlinesOnlyExtension());
// Instantiate the converter engine and start converting some Markdown!
$converter = new MarkdownConverter($environment);
echo $converter->convert('Hello, this is **some Markdown**');
This will be more reliable than parsing HTML with regular expressions.
(I'm the maintainer of league/commonmark and would be happy to answer any follow-up questions you might have in the comments)
I updated my filter, following Colin O'Dells answer. This way, it is more robust, and it will allow the usage or creation of more CommonMark extensions in the future.
# src\Twig\CustomTwigExtension.php
<?php
declare(strict_types=1);
namespace App\Twig;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
use League\CommonMark\MarkdownConverter;
use Twig\Extension\AbstractExtension;
use Twig\Markup;
use Twig\TwigFilter;
use Webmozart\Assert\Assert;
class CustomTwigExtension extends AbstractExtension
{
private const CONFIG = [
'default' => [CommonMarkCoreExtension::class],
'inline' => [InlinesOnlyExtension::class],
];
/**
* #return TwigFilter[]
*/
public function getFilters(): array
{
return [
new TwigFilter('custom_markdown', [$this, 'customMarkdown']),
];
}
public function customMarkdown(string $str, string $configName = 'default'): Markup
{
$env = new Environment();
foreach ($this->getExtensions($configName) as $extension) {
$env->addExtension($extension);
}
$converter = new MarkdownConverter($env);
$html = $converter->convert($str)->getContent();
return new Markup($html, 'UTF-8');
}
/**
* #return ConfigurableExtensionInterface[]
*/
private function getExtensions(string $configName): array
{
Assert::keyExists(self::CONFIG, $configName);
$extensions = [];
foreach (self::CONFIG[$configName] as $extension) {
$extensions[] = new $extension();
}
return $extensions;
}
}
It is called like this in the templates:
{{ markdown_content|custom_markdown }} {# to use the default markdown configuration #}
{{ markdown_content|custom_markdown('inline') }} {# to remove all paragraph tags from the result #}
I want to serve several images which are not avaible in public folder (web) using php for example path/to/myphp/script.php?image=imagereference&some=parameter
To improve performances and not using this approach I made a twig extension to do that.
<?php
#src/MyBundle/Service/DisplayImage.php
namespace MyBundle\Service;
#https://symfony.com/blog/new-in-symfony-2-4-the-request-stack
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
class DisplayImage
{
private $entityManagerInterface;
private $requestStack;
public function __construct(EntityManagerInterface $entityManagerInterface, RequestStack $requestStack)
{
$this->entityManagerInterface = $entityManagerInterface;
$this->requestStack = $requestStack;
}
public function show(int $ref)
{
$photoPath = __DIR__ ."/../photoFolder/".$ref.".jpg";
$file = file_get_contents($photoPath);
$response = new Response();
$response->headers->set('Content-Type', 'image/jpeg');
$response->setContent($file);
return $response;
}
}
And into my twig template
{# src/MyBundle/Ressources/views/DisplayImage.html.twig #}
{% for ref in refs %}
<img src="{{ show(ref) }}"/>
{% endfor %}
But it doesn't work because the response returned is not a valid src path.
The only way I found is to base 64 encode the response returned
<?php
return "data:image/jpeg;base64," . base64_encode($file);
So my question is how generate URL that target my twig extension?
Something like path/to/twigExtension.php?ref=ref calls show(ref)
Maybe it's not the good way to achieve that.
This is a two step process.
you need to create to a valid link to each image (which will be a route to some kind of ImageController::show(id) controller-action)
The ImageController that takes the id (or imagename) and shouts out the binary content of the image file (or throws an error if a user is not granted to download the image). You can return a BinaryFileResponse here. Take a look here.
Hey everybody I am just trying to learn Twig and having a problem getting it to work at all. There are no errors returned in the web server or on the CLI but it just doesn't render the template at all. I installed twig using composer.
<?php
require_once 'vendor/autoload.php';
class xltwig {
private static $twig;
public static function twig() {
$templateDir = __DIR__.'/twig/en_us';
if ( ! isset(self::$twig) ) {
$loader = new Twig_Loader_Filesystem(array($templateDir));
self::$twig = new Twig_Environment($loader);
}
return self::$twig;
}
}
$twig = xltwig::twig();
$twig->render("index.html",array('pageTitle' => 'test'));`
index.html is simply:
<h1>{{ pageTitle }}</h1>
The Twig render method returns a string with the rendered template content, it doesn't output it. The documentation shows it used with echo, and points out:
The display() method is a shortcut to output the template directly.
Is there any way to use something like this?
$foo = "{{ object|filter }}";
Because I'm trying to write a dynamic image converter that needs to output something like the example, but when in my twig I use {{ foo }}, it just outputs a raw string {{ object|filter }} instead of executhing the filter on the object as intended.
I've tried to use {{ foo | raw }} but same result.
What I'm trying to do exactly
CONTROLLER
$image = $em->getRepository('AcmeDemo:Media')->find($id);
$image_src = sprintf("{{ %s | imagine_filter('%s') }}", $image->getWebPath(), 'front_small');
return $this->render('image.html.twig', array(
'image_src' => $image_src
));
TWIG
<img src="{{ image_src }}"/>
So, I have a twig function inside a PHP variable $image_src, that Twig function could be, once formatted with sprintf {{ '/uploads/foo.jpg' | imagine_filter('front_small') }}.
That is a string for now, because it's inside a php variable $image_src, that variable is sent to my Twig template with the name image_src so, for now it is a string as I've said, if I do
My variable contains "{{ image_src }}" It will output a string that says:
My variable contains "{{ '/uploads/foo.jpg' | imagine_filter('front_small') }}"
Because, as I've said, image_src is just a string, but I want to acutally execute inside my Twig, the string that contains image_src, because yes, it is a string (to the eyes of the compiler) but we all know it is or it is pretended to be a Twig function (because of the syntax).
So, why | raw will not work?, because it is inteded to be used with strings containing HTML code, if it were HTML syntax it would work, but it's a Twig syntax, so It doesn't work.
Resuming, there should be a | compile twig function that executes Twig code inside a variable like | raw does with HTML, but, as this function doesn't exists, I'm wondering if there's a way to achieve it...
As #joshua said, it's like a Javascript eval.
I hope I've explained good what is the problem and what I need.
EDIT
I've used my own twig extension Compile in order to achieve what I needed.
class CompileExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
'compile' => new \Twig_Filter_Method($this, 'compile', array(
'needs_environment' => true,
'needs_context' => true,
'is_safe' => array('compile' => true)
)),
);
}
public function compile(\Twig_Environment $environment, $context, $string)
{
$loader = $environment->getLoader();
$compiled = $this->compileString($environment, $context, $string);
$environment->setLoader($loader);
return $compiled;
}
public function compileString(\Twig_Environment $environment, $context, $string)
{
$environment->setLoader(new \Twig_Loader_String());
return $environment->render($string, $context);
}
public function getName()
{
return 'compile';
}
}
UPDATE
Accepting #Benjamin Paap answer because it does exactly what I wanted in this case with better code, but my custom Twig class works for every situation.
What you want to do is not possible in twig without a TwigExtension which renders your string separately.
But looking at your code you're trying to use the LiipImagineBundle the wrong way. It seems tempting to use it this way, but the correct way to generate a url for your thumbnails would be this:
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$imagemanagerResponse = $this->container
->get('liip_imagine.controller')
->filterAction(
$this->request, // http request
'uploads/foo.jpg', // original image you want to apply a filter to
'my_thumb' // filter defined in config.yml
);
// string to put directly in the "src" of the tag <img>
$cacheManager = $this->container->get('liip_imagine.cache.manager');
$srcPath = $cacheManager->getBrowserPath('uploads/foo.jpg', 'my_thumb');
// ..
}
}
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service
I have a system that uses Symfony, and is connected to a (citizen ct-s2000) POS printer.
What I currently do is render the string to send to the printer, using the twig service:
$this->templatingService->render('SamPosBundle:Ticket:template1.html.twig', array('order' => $order))
and send this to the printer using fwrite, after that I feed the paper 1 line and cut the paper using:
fwrite($handle, chr(hexdec('0A')));
fwrite($handle, chr(hexdec('1B')).chr(hexdec('69')));
This all works like a charm, however.
Now I am looking for a way to send the escape codes from WITHIN the twig template
so that I can use the codes to underline etc., and finally cut the paper, from inside the twig template.
I'm sure this would involve a twig extension to create an additional filter, which I know how to make, I just don't know WHAT EXACTLY it should do or how to go about the conversion from twig to escape code which would be picked up by fwrite
I've been looking for 2 days, and I really can't seem to figure this out on my own, so any help would be greatly appreciated.
Configure your twig extension:
services:
your.twig.pos_printer_extension:
class: Your\CustomBundle\Twig\POSPrinterExtension
tags:
- { name: twig.extension }
Create your extension:
<?php
namespace Your\CustomBundle\Twig;
class POSPrinterExtension extends \Twig_Extension
{
public function getGlobals()
{
return [
'some_constant' => chr(hexdec('0A'))
];
}
public function getFilters()
{
return [
'bold' => new \Twig_Filter_Method($this, 'bold')
];
}
public function bold($text)
{
return chr(hexdec('0B')) . $text . chr(hexdec('0A'));
}
}
And finally use it in your twig templates:
{{ some_constant }}
{{ receipt.amount | bold }}
You can apply filters to an entire block too:
{% filter bold %}
Dear {{ name }},
{% endfilter %}
Obviously I don't know the correct escaped chars but you got the idea right?
UPDATE (I'm writing this to avoid downvotes)
My example is using Twig_Filter_Method which is deprecated since 1.12 (to be removed in 2.0), you should be using Twig_SimpleFilter instead.
Its alive, just the syntax for the getFilters() was slightly different:
public function getGlobals()
{
return [
'ticket_cut' => chr(hexdec('1B')).chr(hexdec('69'))
];
}
public function getFilters()
{
return array(
new \Twig_SimpleFilter('ticketBold', array($this, 'ticketBold')),
);
}
public function ticketBold($string)
{
return chr(hexdec('1B')).chr(hexdec('45'))."1".$string.chr(hexdec('1B')).chr(hexdec('45'))."0";
}
Thx for pointing me in the right direction!