Symfony 3 - Serve image within twig extension - php

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.

Related

Using a PHP function in Twig

I have a PHP code to add a new class for my Twig template in my common controller in: "opencart\htdocs\catalog\controller\common\cart.php"
The code should check if the Device is Mobile or not.
function onStart()
{
// Anonymous Class only working on PHP7
$this['code'] = new class {
public function MobileDetect() {
return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo
|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i"
, $_SERVER["HTTP_USER_AGENT"]);
}
};
}
But now I don´t know how to address that function correctly from my twig side at:
opencart\htdocs\catalog\view\theme\default\template\common\cart.twig
I tried something like this, but it didn´t seem to work:
{% if code.MobileDetect() is defined %}
If a device is mobile I want to use a completely different HTML construct.
What you need to do is create your own Twig function:
https://twig.symfony.com/doc/3.x/advanced.html#functions
So using their examples you should be able to do something like
$function = new \Twig\TwigFunction('MobileDetect', function () {
return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo
|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i"
, $_SERVER["HTTP_USER_AGENT"]);
});
$twig->addFunction($filter);
Then call it like
{% if MobileDetect() %}

EasyAdmin3 / Symfony / Twig - url generator with route name and params in Twig template

I'm trying to generate EasyAdmin3 url inside my template with some params, but for some reason they are not present in a controller.
Twig template:
xxx
yyy
Error with missing EA context:
zzz
Controller:
/**
* #Route("/admin/something/{id}", name="rounte_name")
*/
public function xyz($id = null, Request $request, AdminContext $context)
{
dd($_GET['id'], $request->request->all(), $context->getRequest()->request->all());
...
}
The $_GET['id'] works, but request and context are empty [].
Any idea how to generate route by name with params?
Thanks
I don't think you need the ea_url() helper function if you are just generating regular named routes in twig. You should be able to use the path() twig extension provided by Symfony.
{{ path(route_name, route_parameters = [], relative = false) }}
If you are trying to create a link to an EasyAdmin controller action, then you can use the ea_url() helper, but you have to specify the controller and action as well. Try something like:
{% set url = ea_url()
.setController('App\\Controller\\Admin\\FoobarCrudController')
.setAction('customFooAction')
.setEntityId(entity.instance.id)
.set('myParam', 'baz') %}
Custom Foobar Action
Then in your controller, everything should be available as per usual via the $context variable...
public function customFooAction(AdminContext $context)
{
$entity = $context->getEntity()->getInstance();
$myParam = $context->getRequest()->get('myParam');
}
This works for me, I hope it helps. 🙂

Rendered view is not displaying in my back office in PS 1.7

I am trying to create a view in the back office tab that I created in the installation of my Module. My Module adds the tab like this:
protected function _installTabs()
{
if(!$tabId = \Tab::getIdFromClassName('IezonPortfolio')) {
$tab = new \Tab();
$tab->class_name = 'IezonPortfolio';
$tab->module = $this->name;
$tab->id_parent = \Tab::getIdFromClassName('ShopParameters');
$tab->active = 1;
foreach (Language::getLanguages(false) as $lang):
$tab->name[(int) $lang['id_lang']] = 'My Portfolio';
endforeach;
return $tab->save();
}
new \Tab((int) $tabId);
return true;
}
This works fine and I can navigate to my Shop Parameters and click the My Portfolio tab. The issue I'm having is that it is blank. My ModuleAdminController looks like this:
class IezonPortfolioController extends ModuleAdminController {
private $_module;
public function __construct()
{
$this->bootstrap = true;
parent::__construct();
$this->_module = \Module::getInstanceByName('iezonportfolio');
}
public function indexAction()
{
return $this->render('#Modules/iezonportfolio/views/templates/admin/display.html.twig', array(
'contents_iezonportfolio' => $this->_module->selectAll()
));
}
}
My display.html.twig just has test in it to see if it would output anything which it didn't. On looking at the Docs it doesn't mention anything other than using the render function and returning it. Any help would be appreciated. I just get a blank Tab.
EDIT: After looking at some of the pre-installed modules and referencing them to the Docs, I saw that I was missing my route configuration. My Controller is in the documented directory set-up: iezonportfolio/controllers/admin/iezonportfolio.php so I made my route like this:
iezonportfolio:
path: iezonportfolio
methods: [GET]
defaults:
_controller: 'IezonPortfolio\Controllers\Admin\Controller::indexAction'
_legacy_controller: 'IezonPortfolioController'
_legacy_link: 'IezonPortfolioController:index'
This has still not yet fixed the blank display so I tried to dig deeper into some other modules and have now updated my display.html.twig to show this:
{% extends '#PrestaShop/Admin/layout.html.twig' %}
{% block content %}
Test
{% endblock %}
This did not fix the blank display either. I hope this addition is useful for future viewers.
This is not how the modern controllers works, you are extending legacy ModuleAdminController, take a look here:
https://github.com/PrestaShop/example-modules
you have a plenty of module examples, here's a little snippet from one of those modules:
declare(strict_types=1);
namespace PrestaShop\Module\DemoControllerTabs\Controller\Admin;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use Symfony\Component\HttpFoundation\Response;
class MinimalistController extends FrameworkBundleAdminController
{
/**
* #return Response
*/
public function indexAction()
{
return $this->render('#Modules/democontrollertabs/views/templates/admin/minimalist.html.twig');
}
}
I recommend you to think wether you want to use, or not, modern controller. It depends on wether you want to sell your module, use it in client projects etc.

Including SVG in Twig template

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>

Sonata Media Bundle - How to extend FormatThumbnail.php

The Sonata Media Bundle you have the thumbnail property on a provider in the config where you can specify either
sonata.media.thumbnail.format
sonata.media.thumbnail.liip_imagine
This all fine and the sonata.media.thumbnail.format one works fine for everything I want to achieve. My problem comes in with what happens within these files.
In the FormatThumbnail.php there is a function called generatePublicUrl where they generate the url of the media file and also the name of the formatted file. They use the media id within the name or url. If you have private files not everyone must be able to see this causes a problem with it is easy to manipulate the id to another id.
I know the public files that will be served will always stay public so if the url can be guessed the user will access the file. For this specific reason I want to try and replace that id with the unique reference that the bundle uses before they create the actual formatted files as this will not be as easy to just change.
I am aware that there are still risks of leaking out data.
I want to change this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getId(), $format, $this->getExtension($media));
}
return $path;
}
to this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
How do I override the file that the bundle just picks up the change?
I have followed the steps on Sonata's site on how to install and set up the bundle using the easy extends bundle. I have my own Application\Sonata\MediaBundle folder that is extending the original Sonata\MediaBundle.
For installation related information have a look through the documentation(https://sonata-project.org/bundles/media/master/doc/reference/installation.html)
However I tried to create my own Thumbnail folder and creating a new FormatThumbnail.php as follows
<?php
namespace Application\Sonata\MediaBundle\Thumbnail;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\MediaProviderInterface;
use Sonata\MediaBundle\Thumbnail\FormatThumbnail as BaseFormatThumbnail;
class FormatThumbnail extends BaseFormatThumbnail
{
/**
* Overriding this to replace the id with the reference
*
* {#inheritdoc}
*/
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
}
But the bundle still generates everything using the id instead of the reference. Is there a more specific way to achieve extending this file and overriding the function?
After looking at a few different bundles and after looking in code I found that they physically have a parameter which is set to use Sonata\MediaBundle\Thumbnail\FormatThumbnail.
The solution is to override the parameter in the config aswell.
#As top level classification in app/config/config.yml
parameters:
sonata.media.thumbnail.format: Application\Sonata\MediaBundle\Thumbnail\FormatThumbnail
This way the custom FormatThumbnail class is injected everywhere it will be used within the bundle.

Categories