En ocasiones necesitamos limitar el tipo de archivo por su extensión o mime-type, SonataMediaBundle permite cambiar globalmente los formatos admitidos, pero no específicamente a un contexto.
Por lo tanto, hay que crear un Custom Provider, donde configurar estas extensiones y mime-types específicos.
Para el ejemplo, creo el proveedor para subida de archivos ZIP.
1º Creamos el proveedor
ZipProvider.php
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\MediaBundle\Provider\BaseProvider;
use Gaufrette\Filesystem;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\CoreBundle\Model\Metadata;
use Sonata\MediaBundle\CDN\CDNInterface;
use Sonata\MediaBundle\Generator\GeneratorInterface;
use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Thumbnail\ThumbnailInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
/**
* Servicio para configurar el formato ZIP que se pueden utilizar para los archivos
* de descarga en cualquier entidad del sitio web.
*
* @author Juanjo García <juanjogarcia@editartgroup.com>
*/
class ZipProvider extends BaseProvider
{
protected $allowedExtensions;
protected $allowedMimeTypes;
protected $metadata;
/**
* @param string $name
* @param \Gaufrette\Filesystem $filesystem
* @param \Sonata\MediaBundle\CDN\CDNInterface $cdn
* @param \Sonata\MediaBundle\Generator\GeneratorInterface $pathGenerator
* @param \Sonata\MediaBundle\Thumbnail\ThumbnailInterface $thumbnail
* @param array $allowedExtensions
* @param array $allowedMimeTypes
* @param \Sonata\MediaBundle\Metadata\MetadataBuilderInterface $metadata
*/
public function __construct($name, Filesystem $filesystem, CDNInterface $cdn, GeneratorInterface $pathGenerator, ThumbnailInterface $thumbnail, array $allowedExtensions = array(), array $allowedMimeTypes = array(), MetadataBuilderInterface $metadata = null)
{
parent::__construct($name, $filesystem, $cdn, $pathGenerator, $thumbnail);
$this->allowedExtensions = $allowedExtensions;
$this->allowedMimeTypes = $allowedMimeTypes;
$this->metadata = $metadata;
}
/**
* {@inheritdoc}
*/
public function getProviderMetadata()
{
return new Metadata($this->getName(), $this->getName().'.description', false, 'SonataMediaBundle', array('class' => 'fa fa-file-archive-o'));
}
/**
* {@inheritdoc}
*/
public function getReferenceImage(MediaInterface $media)
{
return sprintf('%s/%s',
$this->generatePath($media),
$media->getProviderReference()
);
}
/**
* {@inheritdoc}
*/
public function getReferenceFile(MediaInterface $media)
{
return $this->getFilesystem()->get($this->getReferenceImage($media), true);
}
/**
* {@inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper)
{
$formMapper->add('name');
$formMapper->add('enabled', null, array('required' => false));
$formMapper->add('authorName');
$formMapper->add('cdnIsFlushable');
$formMapper->add('description');
$formMapper->add('copyright');
$formMapper->add('binaryContent', 'file', array('required' => false));
}
/**
* {@inheritdoc}
*/
public function buildCreateForm(FormMapper $formMapper)
{
$formMapper->add('binaryContent', 'file', array(
'constraints' => array(
new NotBlank(),
new NotNull(),
),
));
}
/**
* {@inheritdoc}
*/
public function buildMediaType(FormBuilder $formBuilder)
{
$formBuilder->add('binaryContent', 'file');
}
/**
* {@inheritdoc}
*/
public function postPersist(MediaInterface $media)
{
if ($media->getBinaryContent() === null) {
return;
}
$this->setFileContents($media);
$this->generateThumbnails($media);
}
/**
* {@inheritdoc}
*/
public function postUpdate(MediaInterface $media)
{
if (!$media->getBinaryContent() instanceof \SplFileInfo) {
return;
}
// Delete the current file from the FS
$oldMedia = clone $media;
$oldMedia->setProviderReference($media->getPreviousProviderReference());
$path = $this->getReferenceImage($oldMedia);
if ($this->getFilesystem()->has($path)) {
$this->getFilesystem()->delete($path);
}
$this->fixBinaryContent($media);
$this->setFileContents($media);
$this->generateThumbnails($media);
}
/**
* @throws \RuntimeException
*
* @param \Sonata\MediaBundle\Model\MediaInterface $media
*
* @return
*/
protected function fixBinaryContent(MediaInterface $media)
{
if ($media->getBinaryContent() === null) {
return;
}
// if the binary content is a filename => convert to a valid File
if (!$media->getBinaryContent() instanceof File) {
if (!is_file($media->getBinaryContent())) {
throw new \RuntimeException('The file does not exist : '.$media->getBinaryContent());
}
$binaryContent = new File($media->getBinaryContent());
$media->setBinaryContent($binaryContent);
}
}
/**
* @throws \RuntimeException
*
* @param \Sonata\MediaBundle\Model\MediaInterface $media
*/
protected function fixFilename(MediaInterface $media)
{
if ($media->getBinaryContent() instanceof UploadedFile) {
$media->setName($media->getName() ?: $media->getBinaryContent()->getClientOriginalName());
$media->setMetadataValue('filename', $media->getBinaryContent()->getClientOriginalName());
} elseif ($media->getBinaryContent() instanceof File) {
$media->setName($media->getName() ?: $media->getBinaryContent()->getBasename());
$media->setMetadataValue('filename', $media->getBinaryContent()->getBasename());
}
// this is the original name
if (!$media->getName()) {
throw new \RuntimeException('Please define a valid media\'s name');
}
}
/**
* {@inheritdoc}
*/
protected function doTransform(MediaInterface $media)
{
$this->fixBinaryContent($media);
$this->fixFilename($media);
// this is the name used to store the file
if (!$media->getProviderReference()) {
$media->setProviderReference($this->generateReferenceName($media));
}
if ($media->getBinaryContent()) {
$media->setContentType($media->getBinaryContent()->getMimeType());
$media->setSize($media->getBinaryContent()->getSize());
}
$media->setProviderStatus(MediaInterface::STATUS_OK);
}
/**
* {@inheritdoc}
*/
public function updateMetadata(MediaInterface $media, $force = true)
{
// this is now optimized at all!!!
$path = tempnam(sys_get_temp_dir(), 'sonata_update_metadata');
$fileObject = new \SplFileObject($path, 'w');
$fileObject->fwrite($this->getReferenceFile($media)->getContent());
$media->setSize($fileObject->getSize());
}
/**
* {@inheritdoc}
*/
public function generatePublicUrl(MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $this->getReferenceImage($media);
} else {
// @todo: fix the asset path
$path = sprintf('sonatamedia/files/%s/zip.png', $format);
}
return $this->getCdn()->getPath($path, $media->getCdnIsFlushable());
}
/**
* {@inheritdoc}
*/
public function getHelperProperties(MediaInterface $media, $format, $options = array())
{
return array_merge(array(
'title' => $media->getName(),
'thumbnail' => $this->getReferenceImage($media),
'file' => $this->getReferenceImage($media),
), $options);
}
/**
* {@inheritdoc}
*/
public function generatePrivateUrl(MediaInterface $media, $format)
{
if ($format == 'reference') {
return $this->getReferenceImage($media);
}
return false;
}
/**
* Set the file contents for an image.
*
* @param \Sonata\MediaBundle\Model\MediaInterface $media
* @param string $contents path to contents, defaults to MediaInterface BinaryContent
*/
protected function setFileContents(MediaInterface $media, $contents = null)
{
$file = $this->getFilesystem()->get(sprintf('%s/%s', $this->generatePath($media), $media->getProviderReference()), true);
if (!$contents) {
$contents = $media->getBinaryContent()->getRealPath();
}
$metadata = $this->metadata ? $this->metadata->get($media, $file->getName()) : array();
$file->setContent(file_get_contents($contents), $metadata);
}
/**
* @param \Sonata\MediaBundle\Model\MediaInterface $media
*
* @return string
*/
protected function generateReferenceName(MediaInterface $media)
{
return sha1($media->getName().rand(11111, 99999)).'.'.$media->getBinaryContent()->guessExtension();
}
/**
* {@inheritdoc}
*/
public function getDownloadResponse(MediaInterface $media, $format, $mode, array $headers = array())
{
// build the default headers
$headers = array_merge(array(
'Content-Type' => $media->getContentType(),
'Content-Disposition' => sprintf('attachment; filename="%s"', $media->getMetadataValue('filename')),
), $headers);
if (!in_array($mode, array('http', 'X-Sendfile', 'X-Accel-Redirect'))) {
throw new \RuntimeException('Invalid mode provided');
}
if ($mode == 'http') {
if ($format == 'reference') {
$file = $this->getReferenceFile($media);
} else {
$file = $this->getFilesystem()->get($this->generatePrivateUrl($media, $format));
}
return new StreamedResponse(function () use ($file) {
echo $file->getContent();
}, 200, $headers);
}
if (!$this->getFilesystem()->getAdapter() instanceof \Sonata\MediaBundle\Filesystem\Local) {
throw new \RuntimeException('Cannot use X-Sendfile or X-Accel-Redirect with non \Sonata\MediaBundle\Filesystem\Local');
}
$filename = sprintf('%s/%s',
$this->getFilesystem()->getAdapter()->getDirectory(),
$this->generatePrivateUrl($media, $format)
);
return new BinaryFileResponse($filename, 200, $headers);
}
/**
* {@inheritdoc}
*/
public function validate(ErrorElement $errorElement, MediaInterface $media)
{
if (!$media->getBinaryContent() instanceof \SplFileInfo) {
return;
}
if ($media->getBinaryContent() instanceof UploadedFile) {
$fileName = $media->getBinaryContent()->getClientOriginalName();
} elseif ($media->getBinaryContent() instanceof File) {
$fileName = $media->getBinaryContent()->getFilename();
} else {
throw new \RuntimeException(sprintf('Invalid binary content type: %s', get_class($media->getBinaryContent())));
}
if (!in_array(strtolower(pathinfo($fileName, PATHINFO_EXTENSION)), $this->allowedExtensions)) {
$errorElement
->with('binaryContent')
->addViolation('Invalid extensions')
->end();
}
if (!in_array($media->getBinaryContent()->getMimeType(), $this->allowedMimeTypes)) {
$errorElement
->with('binaryContent')
->addViolation('Invalid mime type : '.$media->getBinaryContent()->getMimeType())
->end();
}
}
}
En la línea 68 incluimos la clase del icono que tenemos que utilizar. (si tu versión de SonataAdminBundle utiliza la versión FontAwesome 4.0.3 y se te quedan cortos los iconos de esta versión, consulta mi otro post, sobre como actualizar la versión de FontAwesome usando SonataCoreBundle)
En la línea 254 se define el nombre del archivo a mostrar en el listado de archivos de este proveedor (provider), la ruta es la misma que se utilizar más abajo en el archivo view_zip.html.twig. La información de su localización se detalla junto a este archivo twig.
2º Creamos los Twigs
view_zip.html.twig
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
<img src="{{ asset('uploads/media/sonatamedia/files/admin/zip.png') }}" alt="archivo ZIP" />
En la línea 11 se define la ruta de la imagen que se mostrará cuando un archivo de este proveedor se esté en algún listado. En este caso he utilizado:

thumbnail_zip.html.twig
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
<img {% for name, value in options %}{{name}}="{{value}}" {% endfor %} />
3º Creamos el servicio para el proveedor
services.yml
parameters:
application_sonata_media.zip_class: Application\Sonata\MediaBundle\Provider\ZipProvider
services:
sonata.media.provider.zip:
class: %application_sonata_media.zip_class%
tags:
- { name: sonata.media.provider }
arguments:
- sonata.media.provider.zip
- @sonata.media.filesystem.local
- @sonata.media.cdn.server
- @sonata.media.generator.default
- @sonata.media.thumbnail.format
- ['zip']
- ['application/zip']
calls:
- [ setTemplates, [{helper_view:SonataMediaBundle:Provider:view_zip.html.twig,helper_thumbnail:SonataMediaBundle:Provider:thumbnail_zip.html.twig}]]
Hay que fijarse en las líneas 16 y 17, en la primera se definen las extensiones de archivos (sin .) y en la segunda los mime-types permitidos.
Aquí dejo el enlace a una completa lista de mime-types.
3º Se aplica el nuevo servicio a un contexto
sonata_media.yml
sonata_media:
contexts:
eag_zip:
download:
strategy: sonata.media.security.public_strategy
mode: http
providers:
- sonata.media.provider.zip
formats:
reference: { quality: 100 }
Referencias
Este asunto lo he tratado en 2 lugares:
El icono utilizado se ha cogido de la siguiente librería: