Show result of uploaded file validation to the user in Symfony3

786 views Asked by At

INTRODUCTION

I am developing on Windows 10 Pro with XAMPP [1] which includes PHP v7.0.9.

I am using Symfony v3.1.5, OneupUploaderBundle and Blueimp jQuery upload in order to upload files to server.

While setting things up I followed documentation of OneUpUploaderBundle [2] and jQuery file upload [3], [4].

PROBLEM

I want to upload files to some directory and then check their mime type and validate is file mime type allowed for uploading and after that - move them to custom directory (that can change from file to file), finally i want to persist file path and file name to database.

File upload works fine and files are uploaded to oneup_uploader_endpoint('gallery'). Even custom file Namer works and allows upload to custom directory.

Listeners are called, yet they are displayed in Symfony Profiler Events section Not Called Listeners. I suspect it might be so because of XMLHttpRequest (Ajax requests) that are performed by Blueimp jQuery uploader in order to upload files and display upload progress.

At the moment I use Javascript to filter allowed mime types. It helps, but it is not "trustworthy". For example if there is PDF file that has name test.pdf.jpg javascript will allow it based on extension in whitelist. But what if PDF file has name test.jpg - no one can be sure about real mimetype. So validation on server is needed. I am using mimetype filter on the server side as well.

Problem is - if the files mimetype is not in the list - the file, simply, is not moved in target directory - technically it uploads fine as validation on server happens after full upload. (That is if I understand correctly. Please correct me if I am wrong about this.) And also I do not know how to get/show any message to user about upload being filtered by mimetype filter (white list).

QUESTION

So the question is: how to display feedback to user about file that is not put though white list after file is successfully uploaded by Blueimp jQuery uploader.

CODE

my services.yml

services:
    app.ultra_helpers:
        class: AppBundle\UltraHelpers\UltraHelpers
        arguments: ['@service_container']

    app.upload_listener:
        class: AppBundle\EventListener\UploadListener
        arguments: ["@doctrine.orm.entity_manager", "@session", "@service_container"]
        tags:
            - { name: kernel.event_listener, event: oneup_uploader.pre_upload.gallery, method: onUpload }
            - { name: kernel.event_listener, event: oneup_uploader.post_upload.gallery, method: onPostUpload }

    app.alowed_mimetype_listener:
        class: AppBundle\EventListener\AllowedMimetypeValidationListener
        tags:
            - { name: kernel.event_listener, event: oneup_uploader.validation.gallery, method: onValidate }

    app.upload_unique_namer:
        class: AppBundle\Uploader\Naming\UploadUniqueNamer
        arguments: ["@session"]

my custom Namer

<?php

namespace AppBundle\Uploader\Naming;

use Oneup\UploaderBundle\Uploader\File\FileInterface;
use Oneup\UploaderBundle\Uploader\Naming\NamerInterface;
use Symfony\Component\HttpFoundation\Session\Session;

class UploadUniqueNamer implements NamerInterface
{
    private $session;

    public function __construct(Session $session)
    {
        $this->session = $session;
    }

    /**
     * Creates a user directory name for the file being uploaded.
     *
     * @param FileInterface $file
     * @return string The directory name.
     */
    public function name(FileInterface $file)
    {
        $active_project_name = $this->session->get('active_project_name');
        $active_project_path = $this->session->get('active_project_path');
        $upload_files_path = $active_project_name . $active_project_path;
        $unique_name = uniqid();

        return sprintf('%s/%s_%s',
            $upload_files_path,
            $unique_name,
            $file->getClientOriginalName()
        );
    }
}

my config.yml

oneup_uploader:
    mappings:
        gallery:
            storage:
                type: flysystem
                filesystem: oneup_flysystem.gallery_filesystem

            frontend: blueimp
            enable_progress: true
            namer: app.upload_unique_namer

            allowed_mimetypes: [ image/png, image/jpg, image/jpeg, image/gif ]
            max_size: 10485760s

oneup_flysystem:
    adapters:
        my_adapter:
            local:
                directory: "%kernel.root_dir%/../data"

    filesystems:
        gallery:
            adapter: my_adapter

my upload listener:

<?php

namespace AppBundle\EventListener;

use Oneup\UploaderBundle\Event\PreUploadEvent;
use Oneup\UploaderBundle\Event\PostUploadEvent;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\Container as Container;
use Symfony\Component\HttpFoundation\Session\Session;
use AppBundle\Entity\MyFile;

class UploadListener
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @var Session
     */
    private $session;

    /**
     * @var Container
     */
    private $container;

    /**
     * @var originalName
     */
    protected $originalName;

    public function __construct(EntityManager $entityManager, Session $session, Container $container)
    {
        $this->entityManager = $entityManager;
        $this->session = $session;
        $this->container = $container;
    }

    public function onUpload(PreUploadEvent $event)
    {
        $file = $event->getFile();
        $this->originalName = $file->getClientOriginalName();
    }

    public function onPostUpload(PostUploadEvent $event)
    {
        $ultra = $this->container->get('app.ultra_helpers');
        $file_path = $ultra->filterFileNameFromPath($event->getFile());

        $upload_files_path = $this->session->get('active_project_path');

        $my_file = new MyFile();
        $my_file->setName($upload_files_path);
        $my_file->setFile($file_path);
        $this->entityManager->persist($my_file);
        $this->entityManager->flush();
    }
}

my template that shows upload form upload.html.twig

{% extends 'base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" type="text/css" href="{{ asset('css/blueimp/jquery.fileupload.css') }}" />
    <link rel="stylesheet" type="text/css" href="{{ asset('css/bootstrap/bootstrap.css') }}" />
    <link rel="stylesheet" type="text/css" href="{{ asset('css/bootstrap/bootstrap-theme.css') }}" />
{% endblock %}

{% block content %}
    <div id="box-list" class="clearfix">
        Go to: <a href="{{ path('file_list') }}">File list</a>
    </div>
    <div id="box-upload">
        <div id="box-file-upload">
            {{ form_start(form, {'attr': {'id': 'my-form-upload'}, 'method': 'POST'}) }}
                <div class="row form-message">
                    {{ form_errors(form) }}
                </div>
                <span class="btn btn-success fileinput-button">
                    <i class="glyphicon glyphicon-plus"></i>
                    <span>&nbsp;Choose files...</span>
                    {{ form_row(form.file, {'id': 'file-upload', 'attr': {'name': 'files[]', 'data-url': oneup_uploader_endpoint("gallery") }}) }}
                </span>
            {{ form_end(form) }}
        </div>
        <div id="box-progress">
            <div id="box-progress-bar" style="width: 0%;"></div>
        </div>
        <div id="box-info">
            <p>Upload status...</p>
        </div>
    </div>
{% endblock %}

{% block javascripts %}
    {{ parent() }}
    <script type="text/javascript" src="{{ asset('js/blueimp/jquery.ui.widget.js') }}"></script>
    <script type="text/javascript" src="{{ asset('js/blueimp/jquery.iframe-transport.js') }}"></script>
    <script type="text/javascript" src="{{ asset('js/blueimp/jquery.fileupload.js') }}"></script>
    <script type="text/javascript">
        $(function()
        {
            'use strict';
            $('#file-upload').on('click', function ()
            {
                $('#box-progress-bar').css('width', '1%');
            });

            $('#file-upload').on("fileuploadprocessfail", function(e, data)
            {
                var file = data.files[data.index];
                alert(file.error);
                console.log(file.error);
            });

            $('#file-upload').fileupload({
                dataType: 'json',
                add: function (e, data)
                {
                    var fileName = data.files[0].name;
                    var fileType = data.files[0].name.split('.').pop();
                    var allowedTypes = 'jpg,JPG,jpeg,JPEG,png,PNG,gif,GIF,pdf,PDF';
                    if (allowedTypes.indexOf(fileType) < 0)
                    {
                        $('#box-progress-bar').css('width', '0');
                        $('<p/>').text(fileName).appendTo($('#box-info'));
                        $('<p class="wrong-file-type"/>').text('Invalid file type').appendTo($('#box-info'));
                        return false;
                    }
                    else
                    {
                        $('<p/>').text(fileName).appendTo($('#box-info'));
                        if ($('.button-upload').length == 0)
                        {
                            // disabling file input
                            $('input#file-upload').attr('disabled', true);

                            data.context = $('<button class="button-upload btn btn-primary start"/>').text('Upload')
                                    .appendTo($('#box-info'))
                                    .click(function ()
                                    {
                                        data.context = $('<p class="upload-success"/>').text('Uploading...').replaceAll($(this));
                                        ($('.button-cancel')).remove();
                                        data.submit();
                                    });
                            $('<button class="button-cancel btn btn-warning cancel" />').text('Cancel')
                                    .appendTo($('#box-info'))
                                    .click(function ()
                                    {
                                        $('#box-progress-bar').css('width', '0');
                                        //console.log('testing');
                                        var message = 'Upload canceled';
                                        ($('.button-upload')).remove();
                                        ($('.button-cancel')).remove();
                                        $('<p class="wrong-file-type"/>').text(message).appendTo($('#box-info'));
                                        // enabling file input
                                        $('input#file-upload').attr('disabled', false);
                                    });
                        }
                    }
                },
                progressall: function (e, data)
                {
                    var progress = parseInt(data.loaded / data.total * 100, 10);
                    $('#box-progress-bar').css('width', progress + '%');
                },
                done: function (e, data)
                {
                    data.context.text('Upload finished.');
                    // enabling file input
                    $('input#file-upload').attr('disabled', false);
                }
            });
        });
    </script>
{% endblock %}

Allowed mimetype listener

<?php

namespace AppBundle\EventListener;

use Oneup\UploaderBundle\Event\ValidationEvent;
use Oneup\UploaderBundle\Uploader\Exception\ValidationException;

class AllowedMimetypeValidationListener
{
    public function onValidate(ValidationEvent $event)
    {
        $config = $event->getConfig();
        $file   = $event->getFile();

        if (count($config['allowed_mimetypes']) == 0)
        {
            return;
        }

        $mimetype = $file->getMimeType();

        if (!in_array($mimetype, $config['allowed_mimetypes']))
        {
            throw new ValidationException('error.whitelist');
        }
    }
}

UPDATES

  1. Added allowed mimetype listener code to the question

CONCLUSION

Please advise.

Thank You for your time and knowledge.

0

There are 0 answers