I have the bundle installed and configured with Sonata Admin Bundle, when I try to remove an Image, the image is properly deleted from the folder but not the thumbnail stored in media/cache.
this is my liip_imagine yml:
liip_imagine:
loaders:
loader_s3_thumbnail:
stream:
wrapper: gaufrette://questions_image_fs/
filter_sets:
question_thumb:
cache: default
data_loader: loader_s3_thumbnail
# list of transformations to apply (the "filters")
filters:
thumbnail: { size: [120, 120], mode: outbound }
provider_thumb:
cache: default
data_loader: loader_s3_thumbnail
# list of transformations to apply (the "filters")
filters:
thumbnail: { size: [200, 200], mode: inset }
Any Idea why or how to delete this thumbnails?
Workmate managed to solve it using Liip cachemanager. Here is the code:
Service:
question.admin_bundle.event_listener.delete_thumbnails:
class: QuestionAdminBundle\EventListener\DeleteThumbnails
arguments: [ "#liip_imagine.cache.manager" ]
tags:
- { name: kernel.event_listener, event: vich_uploader.pre_remove, method: postRemove}
Php:
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
[...]
public function __construct(CacheManager $cacheManager)
{
Add a comment to this line
$this->cacheManager = $cacheManager;
}
[...]
public function postRemove(Event $event)
{
$image = $event->getObject();
if ($image instanceof Image){
$this->cacheManager->remove($image->getName());
}
}
Related
My symfony 5.4 project uses aws_s3 + flysystem + liip_imagine.
In aws_s3, I have a PRIVATE bucket: "myBucket" with 3 subfolders :
documents
photos
media
And IAM PERMISSION
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:DeleteObject",
"s3:GetObjectAcl",
"s3:PutObjectAcl",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::myBucket",
"arn:aws:s3:::mybucket/*"
]
My setups :
see : https://github.com/liip/LiipImagineBundle/issues/823
Service.yaml
...
parameters:
uploads_base_url: '%env(AWS_S3_UPLOAD_BASE_URL)%'
services :
Aws\S3\S3Client:
arguments:
-
version: '2006-03-01'
region: '%env(AWS_S3_ACCESS_REGION)%'
credentials:
key: '%env(AWS_S3_ACCESS_ID)%'
secret: '%env(AWS_S3_ACCESS_SECRET)%'
oneup_flysystem.yaml
...
adapters:
aws_s3_adapter:
awss3v3:
client : Aws\S3\S3Client
bucket: '%env(AWS_S3_BUCKET_NAME)%'
options:
ACL: bucket-owner-full-control
filesystems:
aws_s3_system:
adapter: aws_s3_adapter
lipp_imagine.yaml
# https://symfony.com/bundles/LiipImagineBundle/current/cache-resolver/aws_s3.html
# I do not understand everything
...
driver: "gd"
loaders:
aws_s3_loader:
flysystem:
filesystem_service: oneup_flysystem.aws_s3_system_filesystem
data_loader: aws_s3_loader
resolvers:
aws_s3_resolver:
flysystem:
filesystem_service: oneup_flysystem.aws_s3_system_filesystem
root_url: '%uploads_base_url%'
cache_prefix: media/cache
cache: aws_s3_resolver
filter_sets:
squared_thumbnail_small:
quality: 70
filters:
thumbnail:
size: [50, 50]
mode: outbound
twig
# call twig_function assetPresigned
<a href="{{ assetPresigned('photos', player.photoFilename) }}" target="_blank">
<img src="{{ assetPresigned('photos', player.photoFilename ) | imagine_filter('squared_thumbnail_small') }}" alt="photo">
</a>
FileUploadService.php
public function assetPresigned(string $folder, string $filename): string
{
$command = $this->s3Client->getCommand('GetObject', [
'Bucket' => $this->awsS3BucketName,
'Key' => $folder.'/'.$filename,
]);
// RETURN PRESIGNED URL
$urlPresigned = $this->s3Client->createPresignedRequest($command, '+5 minutes');
return ((string) $urlPresigned->getUri());
}
pb 1 :
My problem is that the "squared_thumbnail_small" filter rewrites the url removing the pre-signed signature
results in twig :
href: the image appears on the click because url is pre-signed
img: url loses its signature and therefore is not displayed
nb: it is liip_imagine via "imagine_filter('squared_thumbnail_small) which creates the thumbnail in mybucket/media. At this stage, the thumbnail does not yet appear in mybucket/media because because it has not yet been displayed
question :
How to properly configure my code so that the filter does not remove the presigned signature ?
Here is what I tried
FileUploadService.php -
namespace App\Service;
...
public function assetPresigned(string $folder, string $filename): string
{
# call ImagineFilterService.php
$this->imagineFilter->filter($folder.'/'.$filename);
# ...
$command....
}
ImagineFilterService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Liip\ImagineBundle\Imagine\Data\DataManager;
use Liip\ImagineBundle\Imagine\Filter\FilterManager;
use App\Service\FileUploadService;
# https://symfony.com/bundles/LiipImagineBundle/current/filters.html#dynamic-filters
# I tried to create a filter dynamically based on this documentation
Class ImagineFilterService
public function filter($path)
{
$filter = 'squared_thumbnail_small';
# if photo is not stored in myBucket/media then you save it by applying the filter?
if (!$this->cacheManager->isStored($path, $filter)) {
$binary = $this->dataManager->find($filter, $path);
$filteredBinary = $this->filterManager->applyFilter($binary, $filter);
# In this method, I pass in parameter the resolver
$this->cacheManager->store($filteredBinary, $path, $filter, 'aws_s3_resolver');
}
// ERROR 403!!!
return $this->cacheManager->resolve($path, $filter);
}
I now recover the thumbnails that were already stored in myBucket/media (ok)
But for saving new thumbnails in myBucket/media I got a 403 error.
AWS HTTP error: Client error: PUT resulted in a 403 Forbidden` response:
Access Denied
So I lose the credentials.
This is complex for me and a precise answer (see a piece of code) would help me a lot. I don't know where I'm wrong. I've been stuck for several days
Thanks for your help.
I'm using Liip Imagine Bundle for Symfony 5. I've got it configured, the images are generated and show properly.
However, I have an issues on an event listener for postUpdate and preRemove where I want to delete the image.
The listener itself works fine, but Liip Imagine generates the wrong path for the storage, and thus I'm stuck with hundreds of images that never get deleted properly.
My config is fairly standard:
parameters:
img_path: "/public/assets/img/uploaded"
display_img_path: "/assets/img/uploaded/"
google_secret_key: '%env(GOOGLE_KEY)%'
site_name: '%env(SITE_NAME)%'
liip_img_cache_path: '/media/cache'
liip_imagine:
resolvers:
default:
web_path:
web_root: "%kernel.project_dir%/public"
cache_prefix: "%liip_img_cache_path%"
filter_sets:
cache: ~
listing_show_thumbnails:
quality: 60
filters:
thumbnail: { size: [ 450 ], mode: outbound, allow_upscale: true }
user_profile_public_thumbnail:
quality: 60
filters:
thumbnail: { size: [ 600 ], mode: outbound, allow_upscale: true }
add_single_thumbnail:
quality: 60
filters:
thumbnail: { size: [ 300 ], mode: outbound, allow_upscale: true }
add_single_thumbnail_carousel:
quality: 80
filters:
thumbnail: { size: [ 800 ], mode: outbound, allow_upscale: true }
And they way the listener is setup is also standard.
class CacheImageListener
{
protected $cacheManager;
protected $parameterBag;
public function __construct(CacheManager $cacheManager, ParameterBagInterface $parameterBag)
{
$this->cacheManager = $cacheManager;
$this->parameterBag = $parameterBag;
}
public function postUpdate(LifecycleEventArgs $args)
{
//... some other stuff here
}
public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof UserImage) {
if($this->cacheManager->isStored($entity->getPath(), 'user_profile_public_thumbnail')) {
$this->cacheManager->remove($entity->getPath(), 'user_profile_public_thumbnail');
}
}
// other functions bellow
The path generated by the fucntion
$this->cacheManager->isStored...
is
C:\Bitnami\wampstack-7.4.9-0\apps\coleg/public/media/cache/user_profile_public_thumbnail/6012e6dbf4cad3416fb609e343470c27778f491dd1debc238fa2d9b3676fae007cc439bcb912feaca006db1bc23a6d17759e.jpeg
The first part is correct(C:\Bitnami\wampstack-7.4.9-0\apps\coleg/public/media/cache/user_profile_public_thumbnail/) but the rest is not.
The folder structures is like so
Basically it's missing the "suffix", I suppose you'd call it. Meaning the assets/img/uploaded which is where I store my original images.
I'm pretty sure I have something misconfigured somewhere but I can't put my finger on it.
I just want to validate the extension of image that I upload in Sonata Admin (v3.28.0) with Sonata Media Bundle (v3.10.0) in Symfony (v2.8.32) application.
I've read all similar questions and Sonata documentations, but still don't have a success.
I tried to add constraints to config.yml
sonata_media:
providers:
image:
allowed_extensions:
- 'jpg'
- 'png'
I wonder that it doesn't work as is, because standard FileProvider (that is extended by ImageProvider) has extension check in validate method. But the method is not being called.
So I also tried to create custom provider:
services.yml:
sonata.media.provider.custom:
class: Application\Sonata\MediaBundle\Provider\CustomImageProvider
tags:
- { name: sonata.media.provider }
arguments:
- sonata.media.provider.custom
- #sonata.media.filesystem.local
- #sonata.media.cdn.server
- #sonata.media.generator.default
- #sonata.media.thumbnail.format
- ['jpg', 'png']
- ['image/pjpeg', 'image/jpeg', 'image/png', 'image/x-png']
- #sonata.media.adapter.image.imagick
- #sonata.media.metadata.proxy
calls:
- [ setTemplates, [{helper_view:SonataMediaBundle:Provider:view_image.html.twig,helper_thumbnail:SonataMediaBundle:Provider:thumbnail.html.twig}]]
Application\Sonata\MediaBundle\Provider\CustomImageProvider.php:
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\CoreBundle\Validator\ErrorElement;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\ImageProvider;
class CustomImageProvider extends ImageProvider
{
public function validate(ErrorElement $errorElement, MediaInterface $media)
{
throw new \Exception();
}
}
config.yml:
sonata_media:
contexts:
image:
providers:
- sonata.media.provider.custom
formats:
small: { width: 100 , quality: 70}
big: { width: 500 , quality: 70}
But the validate method is still not being called.
So when I try to load GIF image I get an error:
Length of either side cannot be 0 or negative, current size is x
Do I miss something?
UPDATE
Simple validation can be added right in SomeEntityAdmin class like this:
public function validate(ErrorElement $errorElement, $object)
{
/** #var Media $image */
$image = $object->getImage();
if (!in_array($video->getContentType(), ['image/pjpeg', 'image/jpeg', 'image/png', 'image/x-png'])) {
$errorElement
->with('image')
->addViolation('Invalid file type')
->end()
;
};
}
But it's not a good solution if you want to validate a batch of uploaded images.
How to acknowledge the message and produce a new message at the same time?
When I start my consumer from the command line, the messages will stay in the original queue. But new ones will be created in the new queue, in an infinite loop. Because it keeps consuming the messages that are not being acknowledged.
Even though TRUE is returned in the execute() function of the consumer. Which should acknowledge it, like it says in the documentation.
I am producing messages from a callback inside a consumer. This producer is injected using the standard Symfony DI.
If I remove the method that publishes the new message, the messages are acknowledged just fine...
services.yml
services:
my_importlog_repository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [AppBundle\Entity\MyImportlogEntity]
my_distributor:
class: AppBundle\DistributorImport\MyDistributor
arguments: [#my_importlog_repository,#logger,#old_sound_rabbit_mq.my_download_producer, %my_config%]
my_download:
class: AppBundle\Consumer\MyDownloadConsumer
arguments: [#logger,#old_sound_rabbit_mq.my_extract_producer,#my_distributor,%my_config%]
my_extract:
class: AppBundle\Consumer\MyExtractConsumer
arguments: [#logger,#old_sound_rabbit_mq.my_convert_producer,#my_distributor,%my_config%]
config.yml
# rabbitmq
old_sound_rabbit_mq:
connections:
default:
host: '192.168.99.100'
port: 5672
user: 'guest'
password: 'guest'
vhost: '/'
lazy: false
connection_timeout: 60
read_write_timeout: 60
# requires php-amqplib v2.4.1+ and PHP5.4+
keepalive: false
# requires php-amqplib v2.4.1+
heartbeat: 30
producers:
# my producers
my_download:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_download'}
my_extract:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_extract'}
my_convert:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_convert'}
consumers:
# my consumers
my_download:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_download'}
callback: my_download
qos_options: {prefetch_size: 0, prefetch_count: 1, global: false}
idle_timeout: 60
my_extract:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_extract'}
callback: my_extract
qos_options: {prefetch_size: 0, prefetch_count: 1, global: false}
idle_timeout: 60
MyDownloadConsumer.php
<?php
namespace AppBundle\Consumer;
use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface;
class MyDownloadConsumer implements ConsumerInterface
{
private $logger;
private $producer;
private $distributor;
private $config;
public function __construct(\Symfony\Component\HttpKernel\Log\LoggerInterface $logger, \OldSound\RabbitMqBundle\RabbitMq\Producer $producer, \AppBundle\DistributorImport\MyDistributor $distributor, Array $config)
{
$this->logger = $logger;
$this->producer = $producer;
$this->distributor = $distributor;
$this->config = $config;
}
public function execute(\PhpAmqpLib\Message\AMQPMessage $message)
{
$data = unserialize($message->body);
$this->producer->publish(serialize($data));
return true;
}
}
If I remove
$data = unserialize($message->body);
$this->producer->publish(serialize($data));
It works like it should...
Was able to publish a message from inside my Consumer execute() method, while also acknowledging the current message being consumed. Using the following code.
$message->delivery_info['channel']
->basic_publish(
new AMQPMessage (serialize($data)),
'name_of_my_exchange',
'key.of.my.routing'
);
Publishing directly on the channel of the message that is being consumed.
I'm trying to upload one or more files using OneupUploaderBundle but I can not get it to work since files aren't uploaded and nothing is persisted to DB. I'll explain what I've done til now:
config.yml:
oneup_uploader:
mappings:
recaudos:
frontend: blueimp
storage:
service: ~
type: filesystem
filesystem: ~
directory: %kernel.root_dir%/../web/uploads/recaudos
stream_wrapper: ~
sync_buffer_size: 100K
allowed_mimetypes: [application/msword,image/jpeg,image/pjpeg,image/png,application/pdf,application/vnd.oasis.opendocument.text]
#disallowed_mimetypes: []
error_handler: oneup_uploader.error_handler.noop
# Set max_size to -1 for gracefully downgrade this number to the systems max upload size.
#max_size: 9223372036854775807
use_orphanage: true
enable_progress: true
enable_cancelation: true
namer: oneup_uploader.namer.uniqid
At Twig template only this:
<input id="fileUpload3" class="fileUpload" type="file" name="fileUpload3[]" data-url="{{ oneup_uploader_endpoint('recaudos') }}" multiple />
$(document).ready(function(){
$('fileUpload3').fileupload();
});
And latest this is the Listener for onUpload event:
namespace AppBundle\EventListener;
use Oneup\UploaderBundle\Event\PostPersistEvent;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity;
class UploadListener
{
protected $doctrine;
public function __construct($doctrine)
{
$this->doctrine = $doctrine;
}
public function onUpload(PostPersistEvent $event, Request $request)
{
$session = $request->getSession();
$em = $this->doctrine->getManager();
$request = $event->getRequest();
$tempfile = $event->getFile();
$productoSolicitud = $em->getRepository('SolicitudProducto')->find($session->get('productoSolicitudId'));
$recaudosTramiteProductoSolicitud = new Entity\RecaudosTramitesProductoSolicitud();
$recaudosTramiteProductoSolicitud->setProductoSolicitud($productoSolicitud);
$filenamePart = explode("--", $tempfile->getName());
$pathinfo = pathinfo($tempfile->getName());
$recaudosTramiteProductoSolicitud->setArchivo($pathinfo['filename']);
$em->persist($recaudosTramiteProductoSolicitud);
$em->flush();
}
}
And of course I have service defined also:
appbundle.upload_listener:
class: "AppBundle\EventListener\UploadListener"
arguments: [#doctrine]
tags:
- { name: 'kernel.event_listener', event: "oneup_uploader.post_persist", method: "onUpload" }
Did I miss something? Why file are not uploaded and data is not persisted? As you may see in the attached image there is a post request to _uploader/recaudos/upload, any advice?
I just checked my own OneupUploader implementation:
Rename your function "onUpload" to "onPostUpload" and change your service configuration to this:
appbundle.upload_listener:
class: "AppBundle\EventListener\UploadListener"
arguments: [#doctrine]
tags:
- { name: 'kernel.event_listener', event: oneup_uploader.post_upload, method: onPostUpload }
Should work, works for me.
If you need your original file name you also need an onUpload Method and this:
tags:
# - {...
- { name: kernel.event_listener, event: oneup_uploader.pre_upload, method: onUpload }
Regarding your comment:
//UploadListener
class UploadListener
{
protected $originalName;
public function onUpload(PreUploadEvent $event)
{
$file = $event->getFile();
$this->originalName = $file->getClientOriginalName();
}
public function onPostUpload(PostUploadEvent $event)
{
$fileEntity = new YourFileEntity();
$fileEntity->setYourOriginalNameProperty($this->originalName);
//...
}
}