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: