phpsymfonysonata-adminsonata-media-bundle

Public and private Media of SonataMediaBundle uploaded in different folders (web and private)


I'm using SonataMediaBundle and until now I've been uploading files publicly in the web folder of Symfony. Here is part of my configuration file:

contexts:
    default:  # the default context is mandatory
        providers:
            - sonata.media.provider.dailymotion
            - sonata.media.provider.youtube
            - sonata.media.provider.image
            - sonata.media.provider.file
            - sonata.media.provider.vimeo

        formats:
            small: { width: 100 , quality: 70}
            big:   { width: 500 , quality: 70}
    profile_pics:  # the default context is mandatory
        providers: 
            - sonata.media.provider.dailymotion
            - sonata.media.provider.youtube
            - sonata.media.provider.image
            - sonata.media.provider.file
            - sonata.media.provider.vimeo

        formats:
            small: { width: 100 , quality: 70}
            big:   { width: 500 , quality: 70}

cdn:
    server:
        path: %cdn_server_path% # http://media.sonata-project.org/

filesystem:
    local:
        directory:  %kernel.root_dir%/../web/uploads/media
        create:     false

And in my parameters.yml I have cdn_server_path: /uploads/media/.

This will upload files in the web/uploads/media/contextname/0001/01/***.

Being in the public folder, these files are visible to everyone. I know I can change this folder somewhere in app/Resources/, but that will make ALL of my files private, which I don't want. By private, I mean that they should be served by a controller, somehow.

How can I separate some files being public and others being private? Is there a way to make a certain context upload files differently than another? Or any other way?

Also, when uploading files to the app/Resources folder, they cannot be previewed since their url becomes http://website.com/app/Resources/media/profile_pics/0001/01/85e05ab9685b1745af4e64ff98ef91eed6e4ccdf.jpeg which is not public, because it's not in the web folder, the only public files that Symfony has. How can this be done?

EDIT: I've managed to do the uploading part(check the answer below), but the retrieving part is a bit troublesome. I'm still looking for a way to do it.


Solution

  • Alright I'm almost there. My files are being uploaded to any folder I want, but I have the problem of getting them to be displayed.

    The idea is to create a custom provider that has its own filesystem and adapter services. The problem was that I needed to pass the adapter service the folder in which I want the files to be uploaded. That adapter is passed to the filesystem which is then passed to the provider. The problem with SonataMediaBundle is that it doesn't let you modify the directory of the adapter, so you have to create your own and pass it all the way up to your provider.

    Services:

    sonata.media.provider.private:
        class: MedAppBundle\Services\PrivateFilesProvider
        tags:
          - { name: sonata.media.provider }
        arguments: [%private_provider_name%,@medapp_private.filesystem,@sonata.media.cdn.server,@sonata.media.generator.default,@sonata.media.thumbnail.format]
    medapp_private.filesystem:
        class: MedAppBundle\Services\PrivateFilesystem
        arguments: [@medapp_private.adapter]
    medapp_private.adapter:
        class: MedAppBundle\Services\PrivateFileAdapter
        arguments: [%private_filesystem_directory%,true]
    

    parameters.yml

    private_filesystem_directory: %kernel.root_dir%/Resources/media
    private_provider_name: sonata.media.provider.private
    

    The custom adapter

    use Sonata\MediaBundle\Filesystem\Local;
    
    class PrivateFileAdapter extends Local
    {
    
    }
    

    The filesystem

    use Gaufrette\Filesystem;
    
    class PrivateFilesystem extends Filesystem
    {
    
    }
    

    And finally, the provider

    use Application\Sonata\MediaBundle\Entity\Media;
    use Gaufrette\Filesystem;
    use Sonata\MediaBundle\CDN\CDNInterface;
    use Sonata\MediaBundle\Generator\GeneratorInterface;
    use Sonata\MediaBundle\Provider\FileProvider;
    
    use Sonata\MediaBundle\Thumbnail\ThumbnailInterface;
    
    class PrivateFilesProvider extends FileProvider
    {
    
        public function getPrivateMedia(Media $media)
        {
            // $path = $this->generatePublicUrl($media, 'reference');
            //  $ppath = $this->generatePrivateUrl($media, "reference");
            $content = $this->getReferenceFile($media)->getContent();
            header('Content-Type: ' . $media->getContentType());
    
    
            return 'data:' .  $media->getContentType() . ';base64,' . base64_encode($content);
    
        }
    }
    

    Then, add this provider to any of your sonata_media contexts. I've added it to the default context, for example:

    sonata_media:
        db_driver: doctrine_orm 
        default_context: default 
        contexts:
            default:  
                providers:
                    - sonata.media.provider.dailymotion
                    - sonata.media.provider.youtube
                    - sonata.media.provider.image
                    - sonata.media.provider.file
                    - sonata.media.provider.vimeo
                    - sonata.media.provider.private
    

    To create a new Media in the private folder:

    $media = new Media();        
    $media->setBinaryContent($file);
    $media->setContext('default');
    $media->setProviderName('sonata.media.provider.private');
    $this->em->persist($media);
    $this->em->flush();
    

    $file is the file content that came from your form. This will create the folder app\Resources\media\default\0001\01 in which the files will be uploaded.

    Now the problem is retrieving them, since they don't have a public link and need to be retrieved in a controller. I've created a getPrivateMedia() method in the provider, but it isn't working as intended.

    EDIT: Noticed that the services could also be defined like so:

    sonata.media.provider.private:
        class: MedAppBundle\Services\PrivateFilesProvider
        tags:
          - { name: sonata.media.provider }
        arguments: [%private_provider_name%,@medapp_private.filesystem,@sonata.media.cdn.server,@sonata.media.generator.default,@sonata.media.thumbnail.format,%kernel.root_dir%]
    
    medapp_private.filesystem:
        class: Gaufrette\Filesystem
        arguments: [@medapp_private.adapter]
    
    medapp_private.adapter:
        class: Sonata\MediaBundle\Filesystem\Local
        arguments: [%private_filesystem_directory%,true]
    

    There is no need to create a custom filesystem or adapter for the provider, just create new services and pass the directory to the adapter, then pass the adapter to the filesystem and the filesystem to the custom provider. This is if you don't need any custom functionality.