• Stars
    star
    157
  • Rank 238,399 (Top 5 %)
  • Language
    PHP
  • Created over 12 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Multiple file uploads, based on the BlueImp jQuery uploader. Makes it very easy to attach one or more files to whatever you're editing. It's also easy to present that list again on a later edit so that existing files can be managed side by side with existing attachments.

PunkAveFileUploaderBundle

Maintainer Needed!

This bundle is no longer actively updated. It is maintained for bugs affecting use with Symfony 2.0.x only. If you are interested in taking over maintenance of this bundle, please contact [email protected]. Thanks!

Introduction

This bundle provides multiple file uploads, based on the BlueImp jQuery file uploader package. Both drag and drop and multiple file selection are fully supported in compatible browsers. We chose BlueImp because it has excellent backwards and forwards browser compatibility.

This bundle is a fairly thin wrapper because the existing PHP uploader class provided by BlueImp is very good already and does so many excellent things straight out of the box. We provided a way to integrate it into a Symfony 2 project.

The uploader delivers files to a folder that you specify. If that folder already contains files, they are displayed side by side with new files, as existing files that can be removed.

The bundle can automatically scale images to sizes you specify. The provided synchronization methods make it possible to create forms in which attached files respect "save" and "cancel" operations.

Note on Internet Explorer

Versions of Internet Explorer prior to 10 have no support for multiple file uploads. However IE users will be able to add a single file at a time and will still be able to build a collection of attached files.

Requirements

  • Symfony2
  • jQuery
  • jQuery UI
  • Underscore

Installation

Symfony 2.0

  1. Add the following line to your Symfony2 deps file:

    [FileUploaderBundle] git=http://github.com/punkave/symfony2-file-uploader-bundle.git target=/bundles/PunkAve/FileUploaderBundle

  2. Modify your AppKernel with the following line:

    new PunkAve\FileUploaderBundle\PunkAveFileUploaderBundle(),

  3. If you do not already have it, add the following line to your autoload.php file:

    'PunkAve' => DIR.'/../vendor/bundles',

  4. Install your vendors:

    bin/vendors install

Symfony 2.2

  1. Add the following line to your composer.json require block: "punkave/symfony2-file-uploader-bundle": "dev-master"

    The standard symfony 2.2 composer.json file has a branch alias that interferes with installing this bundle. You can work around by removing the lines

 "branch-alias": {
            "dev-master": "2.2-dev"
        }
  1. Modify your AppKernel with the following line:

    new PunkAve\FileUploaderBundle\PunkAveFileUploaderBundle(),

  2. Execute composer install

Usage

Your page must contain Underscore templates to render the file list and uploader. You can use our templates like this:

{# Underscore templates for the uploader #}
{% include "PunkAveFileUploaderBundle:Default:templates.html.twig" %}

It is sufficient to do so anywhere in the body. You can copy and modify templates.html.twig if you wish and include it from your own directory. Just don't remove the data-* attributes. The rest of the markup is up to you.

In the Edit Action

Let's assume you have an editAction() method in a controller. You have a form in which you would like to include a list of attached files that work like other fields in the form: you can add more, you can remove existing files, but nothing permanent happens unless the user clicks "save."

The FileUploader service needs a unique folder name for the files attached to a given object. To accomplish this for new objects as well as existing objects, we suggest you follow the "editId pattern," in which a form is assigned a unique, random "editId" for its entire lifetime, including multiple passes of validation if necessary. This allows us to manage file uploads for new objects that don't have their own id yet.

This code takes creat of creating an editId on the first pass through the form and syncs existing files attached to an existing object, if any. The from_folder and to_folder objects specify subdirectories where the attached files will be stored. Later we'll look at how the parent directories of these folders are determined.

(Fetching $posting and validating that the user is allowed to edit that posting is up to you.)

$request = $this->getRequest();

$editId = $this->getRequest()->get('editId');
if (!preg_match('/^\d+$/', $editId))
{
    $editId = sprintf('%09d', mt_rand(0, 1999999999));
    if ($posting->getId())
    {
        $this->get('punk_ave.file_uploader')->syncFiles(
            array('from_folder' => 'attachments/' . $posting->getId(),
              'to_folder' => 'tmp/attachments/' . $editId,
              'create_to_folder' => true));
    }
}

If the user encounters a validation error on their first attempt to complete the action (for instance, a form validation error), you'll want to present the same list of files again. So use the getFiles method to obtain a list of existing files. Make sure you pass that list to your template.

$existingFiles = $this->get('punk_ave.file_uploader')->getFiles(array('folder' => 'tmp/attachments/' . $editId));

(Note that the editId you generate should be highly random to prevent users from gaining control of each other's attachments.)

When the user saves the form and you have just persisted the posting object, you should also sync files back from the temporary folder associated with the editId to the permanent one associated with the posting's id. Since we are done with the temporary folder we ask the file uploader service to remove that folder. We also ask the service to create the destination folder if necessary:

$fileUploader = $this->get('punk_ave.file_uploader');
$fileUploader->syncFiles(
    array('from_folder' => '/tmp/attachments/' . $editId,
    'to_folder' => '/attachments/' . $posting->getId(),
    'remove_from_folder' => true,
    'create_to_folder' => true));

Later you can easily obtain a list of the names of all files attached to an object:

$files = $fileUploader->getFiles(array('folder' => 'attachments/' . $posting->getId()));

However there is a performance cost associated with accessing the filesystem. You will find it more efficient to keep a list of attachments in a Doctrine table, especially if you want to include the first attachment in a list view. Just use getFiles to get the list of filenames and mirror that in your database as you see fit.

In Your Layout

To make the necessary JavaScript available via Assetic (note that you must supply jQuery, jQuery UI and Underscore):

{% javascripts
    '@MyBundle/Resources/public/js/jquery-1.7.2.min.js'
    '@MyBundle/Resources/public/js/jquery-ui-1.8.22.custom.min.js'
    '@MyBundle/Resources/public/js/underscore-min.js'
    '@PunkAveFileUploaderBundle/Resources/public/js/jquery.fileupload.js'
    '@PunkAveFileUploaderBundle/Resources/public/js/jquery.iframe-transport.js'
    '@PunkAveFileUploaderBundle/Resources/public/js/FileUploader.js' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}

You must include the iframe transport for compatibility with IE 9 and below.

In the Edit Template

Let's assume there is an edit.html.twig template associated with the edit action. Here's what it might look like. Note that the render call in your action would pass in the posting object, the editId, the existingFiles array and the isNew flag:

{% extends "MyBundle:Default:layout.html.twig" %}

{% block body %}

{# Underscore templates for the uploader #}
{% include "PunkAveFileUploaderBundle:Default:templates.html.twig" %}

<form class="edit-form" action="{{ path('edit', { id: posting.id, editId: editId }) }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}

    {# Hydrated by javascript #}
    <div class="file-uploader"></div>

    <button class="btn btn-primary" type="submit">{{ isNew ? "Save New Listing" : "Save Changes" }}</button>
    <a class="btn" href="{{ cancel }}">Cancel</a>
    {% if not isNew %}
        <a class="btn btn-danger" href="{{ path('delete', { id: posting.id } ) }}">Delete</a>
    {% endif %}

</form>

<script type="text/javascript">

// Enable the file uploader

$(function() {
    new PunkAveFileUploader({
        'uploadUrl': {{ path('upload', { editId: editId }) | json_encode | raw }},
        'viewUrl': {{ ('/uploads/tmp/attachments/' ~ editId) | json_encode | raw }},
        'el': '.file-uploader',
        'existingFiles': {{ punkave_get_files('tmp/attachments/' ~ editId) | json_encode | raw }},
        'delaySubmitWhileUploading': '.edit-form'
    });
});
</script>
{% endblock %}

Progress Display

There is a simple spinner in template.html.twig. If you choose to provide your own Underscore templates you can replace it. Just make sure you have your own element with a data-spinner="1" attribute.

If you are using template.html.twig, note that you must publish your assets in the usual way for the spinner image to be available:

php app/console assets:install web/

As an alternative, you can write your own code on an interval timer that checks whether the uploading property of the PunkAveFileUploader object is currently set to true and display a spinner on that basis.

Delaying Form Submission Until Uploads Complete

It's not a good idea to let the user submit a form that the file uploader is meant to be part of if uploads are still in progress. You can easily block this by specifying the 'delaySubmitWhileUploading' option as shown above when creating the PunkAveFileUploader JavaScript object:

'delaySubmitWhileUploading': '.edit-form'

Alternatively, you can check the uploading property of the object you create with new PunkAveFileUploader(...) at any time. It will be true if an upload is in progress. The existing implementation of delaySubmitWhileUploading relies on this.

In the Upload Action

In addition to the regular edit action of your form, there must be an upload action to handle file uploads. This action will call the handleFileUpload method of the service to pass on the job to BlueImp's UploadHandler class. Since that class implements the entire REST response directly in PHP, the method does not return.

Here is the upload action:

/**
 *
 * @Route("/upload", name="upload")
 * @Template()
 */
public function uploadAction()
{
    $editId = $this->getRequest()->get('editId');
    if (!preg_match('/^\d+$/', $editId))
    {
        throw new Exception("Bad edit id");
    }

    $this->get('punk_ave.file_uploader')->handleFileUpload(array('folder' => 'tmp/attachments/' . $editId));
}

This single action actually implements a full REST API in which the BlueImp UploadHandler class takes care of uploading as well as deleting files.

Again, handleFileUpload DOES NOT RETURN as the response is generated in native PHP by BlueImp's UploadHandler class.

Setting the allowed file types

You can specify custom file types to divert from the default ones (which are defined in Resources/config/services.yml) by either specifing them in the handleFileUpload method or parameters.yml.

In the handleFileUpload:

$this->get('punk_ave.file_uploader')->handleFileUpload(array(
    'folder' => 'tmp/attachments/' . $editId,
    'allowed_extensions' => array('zip', 'rar', 'tar')
));

In this case the FileUploader service will merge the default extensions with the supplied extensions and make a single regex of it. Using regular expression characters could result in errors.

Parameters.yml: If you have the Symfony standard edition installed you can specify them in app/config/parameters.yml:

file_uploader.allowed_extensions:
    - zip
    - rar
    - tar

Doing this will override the default extensions instead of adding them!

Removing Files

Sooner or later the posting is deleted and you want all of the attachments to be deleted as well.

You can do this as follows:

$this->get('punk_ave.file_uploader')->removeFiles(array('folder' => 'attachments/' . $posting->getId()));

You might want to do that in a manager class or a doctrine event listener, but that's not our department.

Removing Temporary Files

If you choose to follow our editId pattern, you'll want to purge contents of web/uploads/tmp that are over a certain age on a periodic basis. People walk away from websites a lot, so not everyone will click your thoughtfully provided "cancel" action that calls removeFiles() based on the editId pattern.

Consider installing this shell script as a cron job to be run nightly. This shell script deletes files more than one day old, then deletes empty folders:

#!/bin/sh
find /path/to/my/project/web/uploads/tmp -mtime +1 -type f -delete
find /path/to/my/project/web/uploads/tmp -mindepth 1 -type d -empty -delete

(Since the second command is not recursive, the parent folders may stick around an extra day, but they are removed the next day.)

Configuration Parameters

See Resources/config/services.yml in this bundle. You can easily decide what the parent folder of uploads will be and what file extensions are accepted, as well as what sizes you'd like image files to be automatically scaled to.

The from_folder, to_folder, and folder options seen above are all appended after file_uploader.file_base_path when dealing with files.

If file_uploader.file_base_path is set as follows (the default):

file_uploader.file_base_path: "%kernel.root_dir%/../web/uploads"

And the folder option is set to attachments/5 when calling handleFileUpload, then the uploaded files will arrive in:

/root/of/your/project/web/uploads/attachments/5/originals

If the only attached file for this posting is botfly.jpg and you have configured one or more image sizes for the file_uploader.sizes option (by default we provide several useful standard sizes), then you will see:

/root/of/your/project/web/uploads/photos/5/originals/botfly.jpg
/root/of/your/project/web/uploads/photos/5/thumbnail/botfly.jpg
/root/of/your/project/web/uploads/photos/5/medium/botfly.jpg
/root/of/your/project/web/uploads/photos/5/large/botfly.jpg

So all of these can be readily accessed via the following URLs:

/uploads/photos/5/originals/botfly.jpg

And so on.

The original names and file extensions of the files uploaded are preserved as much as possible without introducing security risks.

Limit number of uploads

You can limit the number of uploaded files by setting the max_no_of_files property. You could set this in parameters.yml like this:

parameters:
  file_uploader.max_number_of_files: 4

You'll probably want to add an error handler for this case. In the template where you initialize PunkAveFileUploader set errorCallback

// Enable the file uploader
$(function() {
  new PunkAveFileUploader({
    // ... other required options,

    'errorCallback': function(errorObj) {
      if (errorObj.error == 'maxNumberOfFiles') {
        alert("Maximum uploaded files exceeded!");
      }
    }
  });
});

Limitations

This bundle accesses the file system via the glob() function. It won't work out of the box with an S3 stream wrapper.

Syncing files back and forth to follow the editId pattern might not be agreeable if your attachments are very large. In that case, don't use the editId pattern. One alternative is to create objects immediately in the database and not show them in the list view until you mark them live. This way your edit action can use the permanent id of the object as part of the folder option, and nothing has to be synced. In this scenario you should probably move the attachments list below the form to hint to the user that there is no such thing as "cancelling" those actions.

Notes

The uploader has been styled using Bootstrap conventions. If you have Bootstrap in your project, the uploader should look reasonably pretty out of the box.

The "Choose Files" button allows multiple select as well as drag and drop.

More Repositories

1

phpQuery

This is phpQuery, a PHP port of jQuery selectors, super useful for DOM traversal and functional testing. Originally by Tobiasz Cudnik, who released it on Google Code. We forked it because we need some bug fixes and no commits have been made upstream for quite some time. Please share your phpQuery fixes with us!
PHP
430
star
2

mongo-dump-stream

Pipe entire mongodb databases through the shell, or to a node stream. It's what you wanted mongodump to be.
JavaScript
51
star
3

node-netpbm

node-netpbm scales and converts GIF, JPEG and PNG images asynchronously, without running out of memory, even if the original image is very large. It can also determine the image dimensions and type.
JavaScript
50
star
4

aS3StreamWrapper

An Amazon S3 stream wrapper for PHP with full support for subdirectory trees, multiple protocol names and caching
PHP
43
star
5

grunt-dox

Grunt Plugin for generating Dox
JavaScript
40
star
6

OptimumPHP

Optimum PHP installs and optimizes PHP for Ubuntu and CentOS Linux servers. FastCGI, APC and Apache worker threads are all configured for best performance. PHP is built with all the not-so-"optional" stuff needed by Apostrophe and other PHP framework based web apps such as those built on Symfony. PHP is configured with a more practical set of defaults. Kittens dance.
Shell
37
star
7

jquery-bottomless

jquery-bottomless is an infinite scroll plugin that has been torture-tested by naive users with old versions of IE, which is important because permanently replacing pagination with infinite scroll on a public facing site is a high risk, high benefit proposition. jquery-bottomless has a sweet and simple API.
JavaScript
32
star
8

jquery-rest-admin

A jQuery plugin providing an admin panel for any REST backend. Supports nested panels.
JavaScript
30
star
9

joinr

Performs joins on MongoDB documents and those from compatible databases.
JavaScript
30
star
10

mirror-website

Create a static mirror of a website. The goal is to address the almost universal need for custom processing when mirroring a modern site.
JavaScript
23
star
11

best-practices

P'unk Ave development best practices.
22
star
12

backend-challenge

Backend node.js coding challenge based on the Indego bike share API.
17
star
13

mongoose-uniqueslugs

This plugin guarantees unique slugs for model objects, automatically fixing collisions.
JavaScript
17
star
14

pkLockServer

A distributed lock server built on persistent socket connections, with an emphasis on preventing false positives
PHP
8
star
15

zoltar

Zoltar sees all! Zoltar launches your node-powered sites on demand when you access them by their actual names. No more "node app", no more "http://localhost:3000". Also provides a web console to view the output and start, stop and restart apps.
JavaScript
8
star
16

jquery-selective

jquery-selective provides multiple selection with autocomplete. Items are added to the list by typing part of the label. Items may also be reordered if desired, and the relationship may have fields of its own, like "job title."
HTML
8
star
17

jquery-images-ready

imagesReady waits until the size of certain images is known, then tells you the maximum width, maximum height, and maximum ratio of height to width of all the images. Great for slideshow sizing.
JavaScript
7
star
18

habit

A simple static site generator based on Nunjucks, LESS, and Markdown.
JavaScript
7
star
19

imagecrunch

A native MacOS command line tool for rendering an image file at many sizes. imagecrunch can also crop, convert between formats and automatically rotate photos so that they will face the right way in all web browsers. Much faster than imagemagick for the simple things it does.
Objective-C
7
star
20

jquery-json-call

Make simple JSON calls to a server with jQuery. Both send and receive any valid JSON data with one line of code. Say goodbye to the limitations of the query string format.
JavaScript
7
star
21

loadout

Set up your Mac the P'unk Avenue way with node.js, PHP, MongoDB, MySQL and everything else you need to get serious development done.
Shell
6
star
22

knowtify

A one-page status site
JavaScript
5
star
23

internal-dns

Give each of your coworkers' machines a subdomain of its own, so they can easily test each other's sites on the local network. Powered by dnsmasq, shelljs and prettiest.
JavaScript
5
star
24

jquery-find-safe

Query a selector while ignoring matches within specific nested elements.
JavaScript
5
star
25

jquery-projector

A jQuery slideshow plugin. Progressively enhances a list of items into a slideshow. Progressive enhancement makes it SEO-friendly. Copes intelligently with images of varying heights, imposing a consistent height on the slideshow so your page doesn't "jump." Tested and supported back to at least IE8.
JavaScript
4
star
26

sublime-async-mindset

nodejs async module snippets designed to improve your coding style
3
star
27

send-html-email

Send an HTML file as an email message with the right content type, for testing email clients
JavaScript
3
star
28

whereabouts

Status app for co-workers
JavaScript
3
star
29

async-merge-sort

Efficiently sort an array with an asynchronous comparison function. Suitable for sorts driven by prompting the user for each comparison, for instance.
JavaScript
3
star
30

jquery-radio

jquery-radio lets you painlessly get and set the current value of a group of radio buttons, filling a gap in jQuery's $.val. It is not a replacement for radio buttons. It just makes working with them more pleasant.
JavaScript
3
star
31

frontend-starter

Frontend starting point for P'unk Ave projects
CSS
3
star
32

vineloopr

A web app that loops over vines of a specific tag, and will add new vines with that tag to the loop automatically.
JavaScript
3
star
33

frontend-challenge

P'unk Avenue frontend challenge
3
star
34

jquery-find-by-name

A less bug-prone way to find elements by their name attribute. When the name is dynamic bugs are easily introduced while typing '[name="' + name + '"]'. Just use $.findByName(name) instead.
JavaScript
2
star
35

albedo

node.js app for generating reports from mysql databases
JavaScript
2
star
36

frequently

Execute a function every so often, plus on demand, while avoiding race conditions. Great for implementing "save in background" and other polling operations.
JavaScript
2
star
37

checky

A service that maintains a status check of selected sites
JavaScript
2
star
38

server-status

Live stats on webserver activity, disk space and CPU usage. Meant for scoreboard displays.
JavaScript
2
star
39

poptop

Quick and dirty reports on the most popular URLs and searches in an Apache or Nginx-style log file
JavaScript
2
star
40

html5-video

Converts videos to all suitable formats for use with the "video" element. Will accept a WebM or MP4 video, convert to the other format, and also supply a JPEG fallback image.
JavaScript
2
star
41

libertyjs-oofp

Tom's talk at libertyjs 2016 on Object Oriented Functional Programming
JavaScript
2
star
42

paypal-bulk-refund

Issues refunds for many PayPal REST API transaction IDs quickly.
JavaScript
1
star
43

tag-old-releases

Retroactively tag old npm releases in git. Obviously subjective, use at your own risk.
JavaScript
1
star
44

basecamp-classic-extension

A chrome extension that makes basecamp classic awesomer to work with
JavaScript
1
star
45

Apprenticeship

This repo is a public expostulation of the apprentice program I am helping to build at P'unk Ave.
CSS
1
star
46

find-downtime

A utility application that identifies periods of downtime in Apache-compatible log data
JavaScript
1
star
47

ummon-www

**Work in progess** A web interface for ummon-server
JavaScript
1
star
48

punkave-vagrant-lamp

Fully functional and flexible Vagrant LAMP VM for local development
Puppet
1
star
49

cloud-static

Sync and update folders of web-accessible static content to your local or cloud-based storage via uploadfs, retaining metadata in MongoDB to eliminate latency
JavaScript
1
star
50

nansen

api consumption framework module
JavaScript
1
star
51

s3-upload-missing

Upload contents of given folder to given s3 path, recursively. If a file already exists, do not upload it.
JavaScript
1
star
52

find-big-objects

Find big objects and print out dot paths to the properties that contribute to their size.
JavaScript
1
star
53

haz

Determines if a program exists in the PATH environment variable. Cross platform, including support for checking likely executable file extensions on Windows. Thorough unit tests. Available in synchronous and asynchronous versions.
JavaScript
1
star
54

backbone-utils

This is my toolkit for rapid backbone.js development. There are currently no dependencies, but I might move to making require.js a dependency.
JavaScript
1
star