Symfony4 Twig Extension return image path - symfony

I want to create a TwigExtension Function in Symfony4 in order to display images based on a string property in the view.
I have installed the assets: "symfony/asset": "^4.4",
In lower versions of Symfony I could to this with AssetsHelper::getUrl() -> Returns the public url/path of an asset.
I am not sure how can I achieve this in Symfony4 with "Twig".

The solution is a bit different.
So This is what i did:
In webpackencore config i had to add this in order to have the files copied to the public folder:
.copyFiles({
'from': './assets/img',
'to': 'images/[path][name].[ext]'
})
Then in my extension class the function looks like this:
/**
* #param $type
*
* #return mixed
*/
public function showPaymentTypeIcon($type)
{
$sImagePath = '/build/images/payment-icons';
switch($type) {
case 'card':
default:
$assetImagePath = $sImagePath.'/credit.svg';
break;
case 'sofort':
$assetImagePath = $sImagePath.'/sofort.svg';
break;
}
$oPackage = new Package(new EmptyVersionStrategy());
return $oPackage->getUrl($assetImagePath);
}
So instead of AssetsHelper now we have to use Package as described here: https://symfony.com/doc/current/components/asset.html#asset-packages

Related

Conditionally displaying specific routes on OpenAPI (aka Swagger) documentation generated by API-Platform

I wish to limit the routes displayed by the API-Platform generated OpenAPI documentation based on each route's security attribute and the logged on user's roles (i.e. only ROLE_ADMIN can see the OpenApi documentation).
A similar question was earlier asked and this answer partially answered it but not completely:
This isn't supported out of the box (but it would be a nice
contribution). What you can do is to decorate the
DocumentationNormalizer to unset() the paths you don't want to appear
in the OpenAPI documentation.
More information:
https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification
It appears that DocumentationNormalizer is depreciated and that one should decorate OpenApiFactoryInterface instead.
Attempting to implement, I configured config/services.yaml to decorate OpenApiFactory as shown below.
The first issue is I am unable to "unset() the paths you don't want to appear". The paths exist within the \ApiPlatform\Core\OpenApi\Model\Paths property of \ApiPlatform\Core\OpenApi\OpenApi, but there is only the ability to add additional paths to Paths and not remove them. I've come up with a solution which is shown in the below code which creates new objects and only adds back properties if they do not require admin access, but I suspect that doing so is not the "right way" to do this. Also, I just realized while it removed the documentation from SwaggerUI's routes, it did not remove it from the Schema displayed below the routes.
The second issue is how to determine which paths to display, and I temporarily hardcoded them in my getRemovedPaths() method. First, I will need to add a logon form to the SwaggerUi page so that we know the user's role which is fairly straightforward. Next, however, I will need to obtain the security attributes associated with each route so that I could determine whether a given route should be displayed, however, I have no idea how to do so. I expected the necessary data to be in each ApiPlatform\Core\OpenApi\Model\PathItem, however, there does not appear to be any methods to retrieve it and the properties are private. I also attempted to access the information by using \App\Kernel::getContainer()->get('router'), but was not successful locating the route security attributes.
In summary, how should one prevent routes from being displayed by the API-Platform generated OpenAPI documentation if the user does not have authority to access the route?
config/services.yaml
services:
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments: [ '#App\OpenApi\OpenApiFactory.inner' ]
autoconfigure: false
App/OpenApi/OpenApiFactory
<?php
namespace App\OpenApi;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\OpenApi;
use ApiPlatform\Core\OpenApi\Model\Paths;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use App\Kernel;
class OpenApiFactory implements OpenApiFactoryInterface {
private $decorated, $tokenStorage, $kernel;
public function __construct(OpenApiFactoryInterface $decorated, TokenStorageInterface $tokenStorage, Kernel $kernel)
{
$this->decorated = $decorated;
$this->tokenStorage = $tokenStorage;
$this->kernel = $kernel;
}
public function __invoke(array $context = []): OpenApi
{
//$this->debug($context);
$openApi = $this->decorated->__invoke($context);
$removedPaths = $this->getRemovedPaths();
$paths = new Paths;
$pathArray = $openApi->getPaths()->getPaths();
foreach($openApi->getPaths()->getPaths() as $path=>$pathItem) {
if(!isset($removedPaths[$path])) {
// No restrictions
$paths->addPath($path, $pathItem);
}
elseif($removedPaths[$path]!=='*') {
// Remove one or more operation
foreach($removedPaths[$path] as $operation) {
$method = 'with'.ucFirst($operation);
$pathItem = $pathItem->$method(null);
}
$paths->addPath($path, $pathItem);
}
// else don't add this route to the documentation
}
$openApiTest = $openApi->withPaths($paths);
return $openApi->withPaths($paths);
}
private function getRemovedPaths():array
{
/*
Instead of hardcoding removed paths, remove all paths which $user does not have access to based on the route's security attributes and the user's credentials.
This hack returns an array with the path as the key, and either "*" to remove all operations or an array to remove specific operations.
*/
$user = $this->tokenStorage->getToken()->getUser();
return [
'/guids'=>'*', // Remove all operations
'/guids/{guid}'=>'*', // Remove all operations
'/accounts'=>['post'], // Remove only post operation
'/accounts/{uuid}'=>['delete'], // Remove only delete operation
];
}
private function debug(array $context = [])
{
$this->display($context, '$context');
$openApi = $this->decorated->__invoke($context);
$this->displayGetters($openApi);
$pathObject = $openApi->getPaths();
$this->displayGetters($pathObject, null, ['getPath', 'getPaths']);
$pathsArray = $pathObject->getPaths();
$this->display($pathsArray, '$openApi->getPaths()->getPaths()', true);
$pathItem = $pathsArray['/accounts'];
$this->displayGetters($pathItem);
$getGet = $pathItem->getGet();
$this->displayGetters($getGet, '$pathItem->getGet()', ['getResponses']);
$this->display($getGet->getTags(), '$getGet->getTags()');
$this->display($getGet->getParameters(), '$getGet->getParameters()');
$this->display($getGet->getSecurity(), '$getGet->getSecurity()');
$this->display($getGet->getExtensionProperties(), '$getGet->getExtensionProperties()');
$this->displayGetters($this->kernel, null, ['getBundles', 'getBundle']);
$container = $this->kernel->getContainer();
$this->displayGetters($container, null, ['getRemovedIds', 'getParameter', 'get', 'getServiceIds']);
$router = $container->get('router');
$this->displayGetters($router, null, ['getOption']);
$routeCollection = $router->getRouteCollection();
$this->displayGetters($routeCollection, null, ['get']);
$this->displayGetters($this, '$this');
$this->displayGetters($this->decorated, '$this->decorated');
$components = $openApi->getComponents ();
$this->displayGetters($components, null, []);
}
private function displayGetters($obj, ?string $notes=null, array $exclude=[])
{
echo('-----------------------------------------------------------'.PHP_EOL);
if($notes) {
echo($notes.PHP_EOL);
}
echo(get_class($obj).PHP_EOL);
echo('get_object_vars'.PHP_EOL);
print_r(array_keys(get_object_vars($obj)));
echo('get_class_methods'.PHP_EOL);
print_r(get_class_methods($obj));
foreach(get_class_methods($obj) as $method) {
if(substr($method, 0, 3)==='get') {
if(!in_array($method, $exclude)) {
$rs = $obj->$method();
$type = gettype($rs);
switch($type) {
case 'object':
printf('type: %s path: %s method: %s'.PHP_EOL, $type, $method, get_class($rs));
print_r(get_class_methods($rs));
break;
case 'array':
printf('type: %s method: %s'.PHP_EOL, $type, $method);
print_r($rs);
break;
default:
printf('type: %s method: %s, value: %s'.PHP_EOL, $type, $method, $rs);
}
}
else {
echo('Exclude method: '.$method.PHP_EOL);
}
}
}
}
private function display($rs, string $notes, bool $keysOnly = false)
{
echo('-----------------------------------------------------------'.PHP_EOL);
echo($notes.PHP_EOL);
print_r($keysOnly?array_keys($rs):$rs);
}
}

Symfony 4 : How to delete a file from folder using remove function of the filesystem component

I am trying to remove a file from folder using remove function of the filesystem component in Symfony 4.
Here is my code in the controller:
//Get old logo
$oldlogo = $employer->getLogo();
//If there is a old logo we need to detele it
if($oldlogo){
$filesystem = new Filesystem();
$path=$this->getTargetDirectory().'/public/uploads/logos/'.$oldlogo;
$filesystem->remove($path);
}
private $targetDirectory;
public function __construct($targetDirectory)
{
$this->targetDirectory = $targetDirectory;
}
public function getTargetDirectory()
{
return $this->targetDirectory;
}
Service.yalm:
parameters:
logos_directory: '%kernel.project_dir%/public/uploads/logos'
App\Controller\EmployerController:
arguments:
$targetDirectory: '%logos_directory%'
I have no error message but the file not deleted from the folder.
I using this solution:
in my services.yaml I add:
public_directory: '%kernel.project_dir%/public'
and in my controller I use
$filesystem = new Filesystem();
$path=$this->getParameter("public_directory").'/uploads/logos/'.$oldlogo;
$filesystem->remove($path);

Symfony CMF - Uploading Images and PDF Files

I am using the Symfony CMF Media Bundle to achieve the following. I am having several nodes that can have an image and a downloadable PDF.
I have already figured out that the setImage method has to be implemented like that:
public function setPreviewImage($previewImage)
{
if ($previewImage === null) {
return $this;
}
if (!$previewImage instanceof ImageInterface && !$previewImage instanceof UploadedFile) {
$type = is_object($previewImage) ? get_class($previewImage) : gettype($previewImage);
throw new \InvalidArgumentException(sprintf(
'Image is not a valid type, "%s" given.',
$type
));
}
if ($this->previewImage) {
$this->previewImage->copyContentFromFile($previewImage);
} elseif ($previewImage instanceof ImageInterface) {
$previewImage->setName('previewImage');
$this->previewImage = $previewImage;
} else {
$this->previewImage = new Image();
$this->previewImage->copyContentFromFile($previewImage);
}
return $this;
}
Then in another forum someone was suggested to make this property cascade-persistent. with that hint: https://github.com/symfony-cmf/BlockBundle/blob/master/Resources/config/doctrine-phpcr/ImagineBlock.phpcr.xml#L22. Now i am wondering how and were i can set this option in my configuration.
The next part i am wondering about is the cmf_media_file type. Has anyone out here ever managed to store a PDF into a PHPCR node property?
For any help i would be really thankful.
I figured it out by myself.
For anyone who is using annotations you have to set it up like this:
use Symfony\Cmf\Bundle\MediaBundle\Doctrine\Phpcr\Image;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;
/**
* #var Image
* #PHPCR\Child(cascade="persist")
*/

Get new value of entity field after Doctrine flush

I'm trying to resize an image after persisting an entity with Doctrine. In my Entity code, I'm setting a field to a specific value before the flush and the update :
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->image = $filename.'.png';
}
}
So the image field is supposed to be updated.
Then in my controller, I'd like to do my resize job:
if ($form->isValid())
{
$em->persist($activite);
$em->flush();
//resize the image
$img_path = $activite->getImage();
resizeImage($img_path);
}
However, at this point in the code, the value of $activite->image is still null. How can I get the new value?
(Everything is saved well in the database.)
The EntityManager has a refresh() method to update your entity with the latest values from database.
$em->refresh($entity);
I found my error.
Actually, I was following this tutorial: http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
and at some point they give this code to set the file:
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
// check if we have an old image path
if (isset($this->path)) {
// store the old name to delete after the update
$this->temp = $this->path;
$this->path = null;
} else {
$this->path = 'initial';
}
}
And then after the upload, in the first version (with the random filename), they do :
$this->file = null;
But then in the second version, this code is replace by:
$this->setFile(null);
My problem is that I've tried the two versions to finally come back to the first. However, I forgot to change the line to set the file to null and so everytime my path field was reset to null.
Sorry for this absurdity and thanks for your help.

Symfony2 SonataMediaBundle Audio Config

I need to use SonataMediaBundle to store audio files - mp3 and maybe also somehow conwert them also to ogg.
So basically I need config.
Currently I have this:
sonata_media:
default_context: default
db_driver: doctrine_orm # or doctrine_mongodb, doctrine_phpcr
contexts:
default: # the default context is mandatory
providers:
- sonata.media.provider.dailymotion
- sonata.media.provider.youtube
- sonata.media.provider.image
- sonata.media.provider.file
formats:
small: { width: 100 , quality: 70}
big: { width: 500 , quality: 70}
user:
providers:
- sonata.media.provider.file
formats:
mp3: { quality: 100}
ogg: { quality: 100}
cdn:
server:
path: /uploads/media # http://media.sonata-project.org/
filesystem:
local:
directory: %kernel.root_dir%/uploads/media
create: true
class:
media: Application\Sonata\MediaBundle\Entity\Media
But when I Im using command:
php.exe C:\xampp\htdocs\Radiooo\app\console sonata:media:add sonata.media.provider.file user C:/test.mp3
Im getting file stored like 45914671541816acb68412cc66ba1a71da3ac7a1.mpga
Can you help me what Im doing wrong?
First of all: I'm totally aware of the creation date of this question, but I recently stumbled upon the same problem, where I wanted to upload a mp3 file and just keep the file extension to mp3 instead of switching it to mpga. And as far as I can see, there was no easy solution added to the source code in the last three years ...
I'm pretty sure that my solution is not the best one, but it does the job with a small amount of code to add :)
In my project, I had to upload a file by setting the BinaryContent of a Media object to the Symfony Request class, which leads the Problem to exactly this line:
// FileProvider.php#L483
$guesser = ExtensionGuesser::getInstance();
$extension = $guesser->guess($media->getContentType());
The FileProvider class fetches the instance of the ExtensionGuesser from Symfony and lets him do the work of guessing what the extension for the given ContentType should be:
// MimeTypeExtensionGuesser.php#L623
'audio/mpeg' => 'mpga',
In my opinion it would be great, if we could either add own MimeType->Extension mappings or simply replace the class of the ExtensionGuesser with a small change in the configuration files. But no one knew that three people like us do not want to upload a mp3 file and switch the extension to mpga ... so there was no solution like these.
But we can actually change the className of the FileProvider and just overwrite the method that we want to do something different:
// app/config/services.yml
parameters:
sonata.media.provider.file.class: Application\Sonata\MediaBundle\Provider\FileProvider
// Application\Sonata\MediaBundle\Provider\FileProvider:
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\MediaBundle\Extra\ApiMediaFile;
use Sonata\MediaBundle\Model\MediaInterface;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
use Symfony\Component\HttpFoundation\Request;
/**
* Class FileProvider
*
* #package Application\Sonata\MediaBundle\Provider
*/
class FileProvider extends \Sonata\MediaBundle\Provider\FileProvider
{
/**
* #var array
*/
protected $contentTypeMapping = [
'audio/mpeg' => 'mp3'
];
/**
* Set media binary content according to request content.
*
* #param MediaInterface $media
*/
protected function generateBinaryFromRequest(MediaInterface $media)
{
if (php_sapi_name() === 'cli') {
throw new \RuntimeException('The current process cannot be executed in cli environment');
}
if (!$media->getContentType()) {
throw new \RuntimeException(
'You must provide the content type value for your media before setting the binary content'
);
}
$request = $media->getBinaryContent();
if (!$request instanceof Request) {
throw new \RuntimeException('Expected Request in binary content');
}
$content = $request->getContent();
$extension = $this->getExtension($media->getContentType());
$handle = tmpfile();
fwrite($handle, $content);
$file = new ApiMediaFile($handle);
$file->setExtension($extension);
$file->setMimetype($media->getContentType());
$media->setBinaryContent($file);
}
/**
* Returns the fileExtension for a given contentType
*
* First of all, we have to look at our own mapping and if we have no mapping defined, just use the ExtensionGuesser
*
* #param string $contentType
*
* #return string
*/
private function getExtension($contentType)
{
if (array_key_exists($contentType, $this->contentTypeMapping)) {
return $this->contentTypeMapping[$contentType];
}
// create unique id for media reference
$guesser = ExtensionGuesser::getInstance();
$extension = $guesser->guess($contentType);
if (!$extension) {
throw new \RuntimeException(
sprintf('Unable to guess extension for content type %s', $contentType)
);
}
return $extension;
}
}
Until now it works pretty well and I had no problem with it :)

Resources