bigfork / silverstripe-dropzone
Dropzone.js field for SilverStripe.
Package info
github.com/bigfork/silverstripe-dropzone
Type:silverstripe-vendormodule
pkg:composer/bigfork/silverstripe-dropzone
Requires
- php: ^8.3
- silverstripe/assets: ^3
- silverstripe/framework: ^6
README
A Dropzone.js field for Silverstripe 6 — built for frontend forms, and usable in any PHP-rendered form, including the CMS's page and GridField edit forms. The one exception is React-rendered forms, such as those in the files section — see CMS usage.
Built on @deltablot/dropzone, the maintained fork of the
now-abandoned dropzone package.
Requirements
- Silverstripe 6 /
silverstripe/framework^6 - PHP 8.3+
For Silverstripe 5, use the 2.x release line.
Installation
composer require bigfork/silverstripe-dropzone
Usage
use Bigfork\SilverStripeDropzone\DropzoneField; $field = DropzoneField::create('Attachments', 'Attachments') ->setAllowedMaxFileNumber(5) ->setFolderName('uploads/attachments') ->setDropzoneConfigOption('chunking', true);
Config options are listed here: https://doc.dropzone.deltablot.app/docs/configuration. Most config options should already work, support for some hasn't been added yet.
Maximum file size
setAllowedMaxFileSize() takes a size in bytes or ini format, and applies to a whole file:
$field->setAllowedMaxFileSize('20m'); // or per extension / app category, as Upload_Validator supports: $field->setAllowedMaxFileSize(['*' => '20m', 'zip' => '200m', '[image]' => '5m']);
Without chunking, the size you set is capped at whatever PHP will accept in a single request —
min(upload_max_filesize, post_max_size) — because that's the limit the upload actually has to fit
inside. This is core Upload_Validator behaviour and it's the right answer for a normal upload.
With chunking enabled, that cap is lifted, because a chunked upload never sends the whole file in one request:
$field = DropzoneField::create('Attachments') ->setDropzoneConfigOption('chunking', true) ->setAllowedMaxFileSize('200m'); // works even if upload_max_filesize is 10M
The per-request limit is then enforced against each individual chunk instead, and chunkSize is
capped at what PHP will accept so you can't accidentally configure chunks that PHP rejects. The order
you call setAllowedMaxFileSize() and enable chunking in doesn't matter.
Two things to be aware of when accepting large files this way:
- Your web server has its own limit, and PHP can't see it. nginx's
client_max_body_sizedefaults to 1MB, which will reject even the chunks — raise it above your chunk size. (Apache'sLimitRequestBodydefaults to unlimited.) - The final chunk's request does the reassembly, on top of receiving its own chunk: it
concatenates every chunk, hashes the result and writes it into the asset store. That one request is
the one that may need more
max_execution_time.
If you replace the field's validator with setValidator(), use a subclass of
Bigfork\SilverStripeDropzone\DropzoneUploadValidator — a plain Upload_Validator can't lift the
cap, so combining one with chunking throws rather than silently capping your uploads.
Chunks from uploads that are abandoned part-way through are swept up when a later upload starts, once
they're older than DropzoneField.chunk_max_age (default 24 hours; set it to 0 to disable the
sweep):
Bigfork\SilverStripeDropzone\DropzoneField: chunk_max_age: 3600
Note that parallelChunkUploads is not supported: whether a chunk is the last one is worked out
by checking whether all the others have arrived, so chunks completing concurrently can both start
reassembling and produce duplicate File records. Leave it at its default of false.
Stuff that works:
- File uploads
- Multiple files per request (
->setDropzoneConfigOption('uploadMultiple', true)) - "Chunked" file uploads (
->setDropzoneConfigOption('chunking', true)) - Removing existing files
- Readonly fields (
->performReadonlyTransformation()) — renders the attached files as a plain list - Thumbnails for attached images
Stuff that doesn't work:
- Re-ordering files — Dropzone.js doesn't appear to support this out of the box
- Parallel chunk uploads (
parallelChunkUploads) — see Maximum file size - React-rendered forms — see below
CMS usage
This field works in any PHP-rendered form, which includes the CMS's page edit forms, GridField
detail forms and inline-editable GridField columns (GridFieldEditableColumns) — so a
DropzoneField returned from getCMSFields() will work as you'd expect. In the CMS the field is
initialised through entwine rather than on DOMContentLoaded, so it also works in forms the CMS
loads, replaces or tears down over AJAX.
Note that GridFieldEditableColumns support only covers existing rows —
GridFieldAddNewInlineButton builds its new rows from a client-side template, so the field's upload
URL would point at a record that doesn't exist yet.
It does not work in forms rendered by silverstripe/admin's React form schema, such as the ones
in the files section, or an Elemental block's inline edit form. The field declares
schemaComponent = 'DropzoneField' but ships no React component, so FormBuilder will throw
Component not found in injector: DropzoneField. Additionally, React forms submit values from
redux-form state rather than from the DOM, so this field's hidden-input value mechanism would be
ignored there regardless. Use
silverstripe/asset-admin's
UploadField in those forms instead.
Styling
There are two stylesheets, and only one of them is ever loaded on a given page:
bundle.css— front-end. Dropzone.js' stockbasic.cssanddropzone.css, plus a rule hiding the field's<input type="file">. Restyle it however you'd restyle Dropzone.js anywhere else.bundle-cms.css— CMS only, loaded viaLeftAndMain.extra_requirements_css. Doesn't use Dropzone.js' stylesheets at all; instead it styles every.dz-*class from scratch in Silverstripe's admin palette, laying the attached files out as a list of rows. Source isclient/src/styles/dropzone-cms.scss.
Contributing
Requires Node 20+ (see .nvmrc) and yarn.
yarn install
yarn build # install + lint + production build
yarn dev # development build
yarn watch # development build, watching
yarn lint # eslint + stylelint
client/dist is committed to the repository and exposed via composer, so any change to
client/src needs a rebuild with the built bundle committed, or it has no effect for consumers.