Code Monkey home page Code Monkey logo

easy-media-bundle's Introduction

Adeliom Quality gate

Easy Media Bundle

A VueJS media-manager for Easyadmin.

Versions

Repository Branch Version Symfony Compatibility PHP Compatibility Status
2.x 2.x 5.4, and 6.x 8.0.2 or higher New features and bug fixes
1.x 1.x 4.4, and 5.x 7.2.5 or higher No longer maintained

Features

  • Image editor
  • Multi
    • Upload
    • Move
    • Delete
  • Upload by either
    • Using the upload panel
    • Drag&Drop anywhere
    • Click&Hold on an empty area "items container"
    • From a url "images only"
    • From a url rich embed element like Youtube video
  • Preview files before uploading
  • Toggle between random/original names for uploaded files
  • Bulk selection
  • Bookmark visited directories for quicker navigation
  • Change item/s visibility
  • Update the page url on navigation
  • Show audio files info "artist, album, year, etc.."
  • Dynamically hide files / folders
  • Restrict access to path
  • Download selected "including bulk selection"
  • Directly copy selected file link
  • Use the manager
    • from modal
    • with any wysiwyg editor
  • Auto scroll to selected item using "left, up, right, down, home, end"
  • Lock/Unlock item/s.
  • Filter by
    • Folder
    • Image
    • Audio
    • Oembed
    • Video
    • text/pdf
    • application/archive
    • Locked items
    • Selected items
  • Sort by
    • Name
    • Size
    • Last modified
  • Items count for
    • All
    • Selected
    • Search found
  • File name sanitization for
    • Upload
    • Rename
    • New folder
  • Disable/Enable buttons depend on the usage to avoid noise & keep the user focused
  • Shortcuts / Gestures
    • If no more rows available, pressing down will go to the last item in the list "same as native file manager".
    • When viewing a audio/video file in the preview card, pressing space will play/pause the item instead of closing the modal.
    • Double click/tap
      • any file of type audio/video/oembed will open it in the preview card "same as images".
      • any file of type application/archive will download it.
    • All the left/right gestures have their counterparts available as well.
    • Pressing esc while using the image editor wont close the modal but you can dbl click/tap the modal background to do so. "to avoid accidentally canceling your changes".

To stop interfering with other keydown events you can toggle the manager listener through EventHub.fire('disable-global-keys', true/false).

Installation with Symfony Flex

Add our recipes endpoint

{
  "extra": {
    "symfony": {
      "endpoint": [
        "https://api.github.com/repos/agence-adeliom/symfony-recipes/contents/index.json?ref=flex/main",
        ...
        "flex://defaults"
      ],
      "allow-contrib": true
    }
  }
}

Install with composer

composer require agence-adeliom/easy-media-bundle

Without Symfony Flex

# config/packages/easy_media.yaml

twig:
  form_themes:
    - '@EasyMedia/form/easy-media.html.twig'

doctrine:
  dbal:
    types:
      easy_media_type: Adeliom\EasyMediaBundle\Types\EasyMediaType

flysystem:
  storages:
    medias.storage:
      adapter: 'local'
      options:
        directory: '%kernel.project_dir%/var/storage/medias'

easy_media:
  storage_name: medias.storage
  media_entity: App\Entity\EasyMedia\Media
  folder_entity: App\Entity\EasyMedia\Folder
# config/routes/easy_media.yaml

easy_media:
  resource: '@EasyMediaBundle/Resources/config/routes.xml'
  prefix: /admin

easy_media_public:
  resource: '@EasyMediaBundle/Resources/config/public_routes.xml'
  prefix: /
<?php
// src/Entity/EasyMedia/Folder.php

namespace App\Entity\EasyMedia;

use Adeliom\EasyMediaBundle\Entity\Folder as BaseFolder;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'easy_media__folder')]
class Folder extends BaseFolder
{
}
<?php
// src/Entity/EasyMedia/Media.php

namespace App\Entity\EasyMedia;

use Adeliom\EasyMediaBundle\Entity\Media as BaseMedia;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'easy_media__media')]
class Media extends BaseMedia
{
}

Setup database

Using doctrine migrations

php bin/console doctrine:migration:diff
php bin/console doctrine:migration:migrate

Without

php bin/console doctrine:schema:update --force

Documentation

Manage medias in your Easyadmin dashboard

Go to your dashboard controller, example : src/Controller/Admin/DashboardController.php

<?php

namespace App\Controller\Admin;

...
class DashboardController extends AbstractDashboardController
{
    ...
    
    // Add the custom form theme
    public function configureCrud(): Crud
    {
        return parent::configureCrud()
            ->addFormTheme('@EasyMedia/form/easy-media.html.twig')
        ;
    }
    
    public function configureMenuItems(): iterable
    {
        ...
        yield MenuItem::linkToRoute('Medias', 'fa fa-picture-o', 'media.index');

        ...

Integrate with FOS CKEditor

#config/packages/fos_ck_editor.yaml
fos_ck_editor:
    configs:
        main_config:
            ...
            filebrowserBrowseRoute: media.browse
            filebrowserImageBrowseRoute: media.browse
            filebrowserImageBrowseRouteParameters:
                provider: 'image'
                restrict:
                    uploadTypes:
                        - 'image/*'
                    uploadSize: 5

Integrate with LiipImagineBundle

#config/packages/liip_imagine.yaml
liip_imagine:
  data_loader: easy_media_data_loader
{{ object.media|resolve_media|imagine_filter('filter_name') }}

Field's usage

Usage

use Adeliom\EasyMediaBundle\Admin\Field\EasyMediaField;
...
yield EasyMediaField::new('property', "label")
    // Apply restrictions by mime-types
    ->setFormTypeOption("restrictions_uploadTypes", ["image/*"])
    // Apply restrictions to upload size in MB
    ->setFormTypeOption("restrictions_uploadSize", 5)
    // Apply restrictions to path
    ->setFormTypeOption("restrictions_path", "users/" . $userID)
    // Hide fiels with extensions (null or array)
    ->setFormTypeOption("hideExt", ["svg"])
    // Hide folders (null or array)
    ->setFormTypeOption("hidePath", ['others', 'users/testing'])
    // Enable/Disable actions
    ->setFormTypeOption("editor", true)
    ->setFormTypeOption("upload", true)
    ->setFormTypeOption("bulk_selection", true)
    ->setFormTypeOption("move", true)
    ->setFormTypeOption("rename", true)
    ->setFormTypeOption("metas", true)
    ->setFormTypeOption("delete", true)
    ;

Twig usage

# Render the media
{{ easy_media(object.media, format, options) }} // By default format is the reference file and options

# Examples :
{{ easy_media(object.media, "reference") }}

{{ easy_media(object.media, "cover_full", {'class': 'myclass'}) }} 

## For images 
{{ easy_media(object.media, "cover_full", {'loading': "lazy"}) }} 
{{ easy_media(object.media, "cover_full", {'picture': ["cover_full__2xl","cover_full__xl","cover_full__lg","cover_full__sm","cover_full__xs"]}) }}
{{ easy_media(object.media, "cover_full", {'srcset': ["cover_full__2xl","cover_full__xl","cover_full__lg","cover_full__sm","cover_full__xs"]}) }}
{{ easy_media(object.media, "cover_full", {'loading': "lazy", 'srcset': {'(max-width: 500px)': 'cover_full__2xl', '(max-width: 1200px)': 'cover_full__xl'}}) }}

## For oembed 
{{ easy_media(object.media, "reference") }}
{{ easy_media(object.media, "reference", {'responsive': true}) }}

## For video 
{{ easy_media(object.media, "reference") }}
{{ easy_media(object.media, "reference", {"responsive" : true, "controls" : true, "autoplay" : true}) }}

# Get media path
{{ easy_media_path(object.media, format) }} // By default format is the reference file

# Get media URL
{{ object.media|resolve_media }}

# Get media metadatas
{{ object.media|media_meta }}

# Get single media metadata
{{ object.media|media_meta('key') }}

# Get complete media informations
{{ object.media|media_infos }}

# Get test file type
# type_to_test: can be a mime_type or 
# oembed for any embed type
# image for any image type
# pdf for pdf files
# compressed for archives files
{{ file_is_type(object.media, type_to_test) }}

# Get mimetype icon (font-awesome)
{{ mime_icon("text/plain") }}

You can override media render with twig

  • For images : @EasyMedia/render/image.html.twig
  • For oembed : @EasyMedia/render/oembed.html.twig
  • For video : @EasyMedia/render/oembed.html.twig

Manage medias and folders programmatically

/* @var EasyMediaManager $manager */

# Get media by id or null
$media = $manager->getMedia($id);

# Get folder by id or null
$folder = $manager->getFolder($id);

# Get folder by path
$folder = $manager->folderByPath($path);

# Create a folder
$folder = $manager->createFolder($folderName, $path = null)

# Create a media
# $source can be a UploadedFile, File, Image URL, Oembed URL, Base64 URI
$folder = $manager->createMedia($source, $path = null, $name = null)

# Save a folder or media
$manager->save($entity, $flush = true);

# Delete a folder or media
$manager->delete($entity, $flush = true);

Use the Doctrine type (optional)

It automatically converts the stored path into a Media entity

# config/packages/doctrine.yaml
doctrine:
  dbal:
    ...
    types:
      easy_media_type: Adeliom\EasyMediaBundle\Types\EasyMediaType

In your entity

class Article
{
    #[ORM\Column(type: 'easy_media_type', nullable: true)]
    private Media|string|null $file;
    
    ...

Configurations

# config/packages/easy_media.yaml
easy_media:
    storage:              '%kernel.project_dir%/public/upload'
    base_url:             /upload/
    
    # ignore any file starts with "."
    ignore_files:         '/^\..*/'
    
    # remove any file special chars except
    # dot .
    # dash -
    # underscore _
    # single quote ''
    # white space
    # parentheses ()
    # comma ,
    allowed_fileNames_chars: '\._\-\''\s\(\),'
    
    # remove any folder special chars except
    # dash -
    # underscore _
    # white space
    #
    # to add & nest folders in one go add '\/'
    # avoid using '#' as browser interpret it as an anchor
    allowed_folderNames_chars: _\-\s
    
    # disallow uploading files with the following mimetypes (https://www.iana.org/assignments/media-types/media-types.xhtml)
    unallowed_mimes:
        # Defaults:
        - php
        - java
    
    # disallow uploading files with the following extensions (https://en.wikipedia.org/wiki/List_of_filename_extensions)
    unallowed_ext:
        # Defaults:
        - php
        - jav
        - py

    extended_mimes:
        # any extra mime-types that doesnt have "image" in it
        image:                # Required
            # Default:
            - binary/octet-stream
        # any extra mime-types that doesnt have "compressed" in it
        archive:              # Required
            # Defaults:
            - application/x-tar
            - application/zip
    
    # display file last modification time as
    last_modified_format: Y-m-d
    
    # hide file extension in files list
    hide_files_ext:       true
    
    # loaded chunk amount "pagination"
    pagination_amount:    50

Events

type event-name description
JS
modal-show when modal is shown
modal-hide when modal is hidden
file_selected (when inside modal) get selected file url
multi_file_selected (when inside modal) get bulk selected files urls
folder_selected (when inside modal) get selected folder path
Symfony
em.file.uploaded($file_path, $mime_type, $options) get uploaded file storage path, mime type
em.file.saved($file_path, $mime_type) get saved (edited/link) image full storage path, mime type
em.file.deleted($file_path, $is_folder) get deleted file/folder storage path, if removed item is a folder
em.file.renamed($old_path, $new_path) get renamed file/folder "old & new" storage path
em.file.moved($old_path, $new_path) get moved file/folder "old & new" storage path

License

MIT

Authors

Thanks to

ctf0/Laravel-Media-Manager

easy-media-bundle's People

Contributors

arnaud-ritti avatar dependabot[bot] avatar jdadeliom avatar jeandaviddaviet avatar jeromeengeln avatar lucasvigneron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

easy-media-bundle's Issues

BadRequestException when using fosckeditor integration with symfony 6.x

When using fosckeditor integration with symfony 6.x, the 'restrict' config cause a BadRequestException when the value is an array.

Symfony\Component\HttpFoundation\Exception\BadRequestException:
Input value "restrict" contains a non-scalar value.

The $request->query->get('restrict') doesn't accept array type as it was deprecated in 5.x and removed in 6.0.
$request->query->all('restrict') should be used instead.

See : symfony/symfony#41766 (comment)

Multiselect

Is possible to make EasyMediaField multiselect or atleast can i combine somehow EasyMediaField with CollectionField?

btw. thank you alot for great bundle.

greeting Kristijan

Having two folder with the same name create issue when reusing the media

When creating a media, a folder is created.
If I want to create another media with another folder, I can't, because each folder slug must be unique.

Example :

pages/home/media1.jpg
home/media2.jpg

It will create a conflict as media2jpg will have a folder named home-2 for no conflict, but then the path is different from the slug

Error in fileIsType method

In src/Service/EasyMediaHelper, there is this condition inside fileIsType method.

if ((($type && str_contains((string) $type, 'image')) || in_array($type, $mimes['image'] ?? [])) && 'image' !== $compare) {
    return true;
}

This will return true if we compare, for example image/jpeg with video.
I think this is not expected and I'd be glad to open a PR in order to fix this but I don't really understand what this condition should be.

Maybe you can help me here @arnaud-ritti ?

Cannot assign string to property Doctrine\ORM\Mapping\JoinColumnMapping::$nullable of type ?bool and Roadrunner runtime

Hello!

I was facing this error on bin/console doctrine:migrations:diff command:

Cannot assign string to property Doctrine\ORM\Mapping\JoinColumnMapping::$nullable of type ?bool

and the code below fixed the issue. I also tried to reproduce it with a new symfony skeleton project 6.4 with no luck...

use Adeliom\EasyMediaBundle\Entity\Folder as BaseFolder;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'easy_media__folder')]
class Folder extends BaseFolder
{
    #[ORM\ManyToOne(targetEntity: Folder::class, inversedBy: 'children')]
    #[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id', nullable: true)]
    protected ?BaseFolder $parent = null;

    /**
     * @var Collection<self>
     */
    #[ORM\OneToMany(targetEntity: Folder::class, mappedBy: 'parent')]
    protected Collection $children;

    /**
     * @var Collection<Media>
     */
    #[ORM\OneToMany(targetEntity: Media::class, mappedBy: 'folder')]
    protected Collection $medias;
}

Not a big deal, reported it here just in case, maybe you could add attributes to those fields in the BaseFolder.

The second issue is critical - I am using Roadrunner as a runtime and after debugging for few hours I found that the function move_uploaded_file is not working. Could you please use UploadedFile->move() something like that:

$chunkFile = sprintf('%s/%s.part%d', $fileChunksFolder, $filename, $chunkInd);
$file = $request->files->get('file');
// if (!move_uploaded_file($tmpFilePath, $chunkFile)) {
if (!$file->move($fileChunksFolder, $filename . '.part' . $chunkInd)) {
    $errors[] = ['text' => 'Move error', 'name' => $filename, 'index' => $chunkInd];
}

The code above works in my case and probably shouldn't break anything.

I could make a PR if you confirm that these changes would work as expected.

Btw thanks for this bundle and your time!

Media Manager not working when folder has been deleted

When I go to a folder, his folderId is stored inside localStorage. It is used then to access the same folder when opening the media library.
But if I delete the folder, the folderId may still be inside the localStorage.
The bundle should check if the doesn't exist then it should load the root of the media library, cause as of today it prevent to do anything.

media-library-error

The child config "media_entity" under "easy_media" must be configured.

`Executing script cache:clear [KO]
[KO]
Script cache:clear returned with error code 1
!!
!! In ArrayNode.php line 226:
!!
!! The child config "media_entity" under "easy_media" must be configured.
!!
!!
!! 2021-11-10T09:05:21+01:00 [info] User Deprecated: Method "Symfony\Component\HttpKernel\Bundle\Bundle::getContainerExtension()" might add "?ExtensionInterface" as a native return type declaration in the future. Do the same in child class "Adeliom\EasyCommonBundle\EasyCommonBundle" now to avoid errors or add an explicit @return annotation to suppress this message.
!! 2021-11-10T09:05:21+01:00 [info] User Deprecated: Method "Symfony\Component\HttpKernel\Bundle\Bundle::getContainerExtension()" might add "?ExtensionInterface" as a native return type declaration in the future. Do the same in child class "Adeliom\EasyMediaBundle\EasyMediaBundle" now to avoid errors or add an explicit @return annotation to suppress this message.
!!
Script @auto-scripts was called via post-update-cmd

Installation failed, reverting ./composer.json and ./composer.lock to their original content.`

What can be done?

Manager - createFolder > setParent should be called before getPath is used

I think I found a quite impactful logic error in method createFolder from EasyMediaManager.

Folder::getPath is used before setParent has been called, therefore, if the folder should be nested, the path returned (and then created by the filesystem) is wrong (relative to root instead of parent folder).

Here is the code of the function, I've added comments to explain my point :

public function createFolder(?string $name, ?string $path = null): ?Folder
    {
        if ('.' === $path) {
            $path = '';
        }

        $class = $this->getHelper()->getFolderClassName();
        /** @var Folder $entity */
        $entity = new $class();

        if ($name) {
            $entity->setName($name);
        }

        $folder = $this->folderByPath($path);
        if (false === $folder && !empty($path)) {
            $folder = $this->createFolder(basename($path), dirname($path));
        }

        if (!empty($this->getHelper()->getFolderRepository()->findBy(['parent' => $folder, 'name' => $name]))) {
            throw new FolderAlreadyExist($this->translator->trans('error.already_exists', [], 'EasyMediaBundle'));
        }

        // in the next two lines, getPath only returns slug of $entity since it has no parent yet
        if (!$this->filesystem->directoryExists($entity->getPath())) {
            $this->filesystem->createDirectory($entity->getPath(), []);
        }

        $entity->setParent($folder ?: null); // This should be called before
        $this->save($entity);

        return $entity;
    }

This error leads to multiple issue with "File/folder already exists", I would be happy to open a PR if needed.

Probably related to #25

Expose MediaManager's createFrom* methods and/or allow entity modifications before save

There's no point in having a customizable Media entity when the createMedia method creates the entity and then immediately persist is and flushes the DB (which, by the way, is a terrible practice as it can cause unexpected flushes in the middle of a request).

For one it'd be nice if all the createFrom* methods were public for more granular control by user.

The save issue could be solved in several ways:

  • Remove the save method from createMedia since it's the programmer's responsibility to manage entities. You don't necessarily want to always immediately persist (and definitely not flush) an entity you create. This is easiest and IMO most correct but breaks BC.
  • Pass an argument to createMedia on whether to save; perhaps just $flush like you have on other methods. This is a simple solution even if it's not very neat.
  • Add some events to the manager logic that fire right after entity creation, right before save, etc. This would add great verstility, is still correct, and allows most customization (especially if you add events for other things too). It's however somewhat harder, though I'd suggest doing this anyway in addition to one of the above options.

Not view theme in EasyAdmin 4 and Symfony 6

I can't show theme of media manager and i don't know what doing wrong. I have followed the instrucctions up to section Manage medias in your Easyadmin dashboard. Attach screenshot of show media index.

easyMediaScreen

Thanks for your time.

Exception in MediaSubscriber

When moving a media from the manager, an exception is occuring in this Subscriber :

Typed property Adeliom\EasyMediaBundle\EventListener\MediaSubscriber::$manager must not be accessed before initialization

The property $manager is defined, but never initialized, the injected service in the constructor is not used :

class MediaSubscriber implements EventSubscriberInterface
{
    protected \Adeliom\EasyMediaBundle\Service\EasyMediaManager $manager;

    public function __construct(EasyMediaManager $manager)
    {
    }
    // ...
}

EasyMediaManager error after updating

Hello ! Thans a lot for fixing some issues in the latest updates.

I am now getting this error after updating to latest version (2.0.31) :
Adeliom\EasyMediaBundle\Service\EasyMediaManager::__construct(): Argument #1 ($filesystem) must be of type League\Flysystem\FilesystemOperator, League\Flysystem\Local\LocalFilesystemAdapter given

No icons for Download + Upload from URL

There are no icons displayed in views :

  • upload from URL in partials/manager/dropzone.html.twig;
  • download file in parials/card.html.twig

The button close in modals aren't visible as well - class modal-close :before + :after could have a background-color to display the "X"

Adeliom\EasyMediaBundle\Service\EasyMediaManager::__construct(): Argument #1 ($filesystem) must be of type League\Flysystem\FilesystemOperator, League\Flysystem\Local\LocalFilesystemAdapter given, called in /var/www/html/francois-rouan/var/cache/dev/ContainerKp7v7sX/getEasyMedia_ManagerService.php on line 34

After installation of this package, I have the following error during symfony cache:clear and anywhere in front

Adeliom\EasyMediaBundle\Service\EasyMediaManager::__construct(): Argument #1 ($filesystem) must be of type League\Flysystem\FilesystemOperator, League\Flysystem\Local\LocalFilesystemAdapter given, called in /var/www/html/francois-rouan/var/cache/dev/ContainerKp7v7sX/getEasyMedia_ManagerService.php on line 34

My config is :
PHP 8.2
Apache2
Symfony 6.4 LTS

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.