Last updated July 27, 2008. Created by Mr. Pi on July 27, 2008.
Log in to edit this page.
Just like every web application, Drupal suffers from the HTML limitation that for form based file uploads, only a single file can be selected. This is fine is you want to say, attach a picture to a story, but quite tedious for batch uploading, for example for photo galleries (my itch).
There are some ways around this, for example by using archives of files or uploading by FTP. However, in my opinion these solutions are not ideal as they demand too much from the end user. An alternative is to use a Flash based plugin which integrates right into the front-end. One of them is SWFUpload, a nice, small and open source app which limits itself to the selection and uploading of files. It works with standard POST requests in the background and doesn't force a GUI on you, so it integrates nicely with your website. In its most basic form, you can bring up a standard file selection dialog, select one or more files, and start the upload. SWFUpload provides all the hooks and handles to control the uploading and build an HTML/Javascrinpt interface to your own liking.
Using SWFUpload into your Drupal website requires you to jump through some hoops though, and it took me quite some time and digging around on internet to figure out how to do this. With this post, I'd like to share my experience to make it easier for other people to use SWFUpload in their Drupal website. It is not exhaustive, but should get you up to speed. I assume a basic knowledge of Drupal, PHP and website building (my previous experience with either Drupal or SWFUpload was less than a week before I started this project.) This post doesn't give you an out-of-the-box solution, but hopefully it gives enough information to start using SWFUpload quickly so you can tailor it to your own problem.
This post is written for Drupal 5.x. However, I suspect it can be used for other Drupal versions without much change.
Note that the SWFUpload Module for Drupal is not considered here; it is limited to enhancing the Upload module, and, as far as I can tell doesn't work properly at the moment.
Step 1: Adding the SWFUpload files
Throughout its live, SWFUpload has been located at different web pages. The relevant one (at the time of writing) is the Google Code homepage: http://code.google.com/p/swfupload/. From here you can download the SWFUpload-Core package.
We need three files from this package: swfupload.js (from the FlashDevelop dir), swfupload_f9.swf (from the FlashDevelop/Flash9 dir), and the plugin swfupload.cookies.js (from the FlashDevelop/plugins dir). The first two files form the actual SWFUpload package, the cookies plugin will be explained later. Note that you need the Flash9 plugin here, or the method won't always work.
You need to place these files somewhere in your server file hierarchy where they can be accessed by the module you're developing/adapting. I assume you place them in a subdirectory called 'swfupload' in your module directory.
To include the js files, you can use the drupal_add_js() function in your hook_form(), the function that builds your form. Assuming the file location described above, you would add the following two lines (where module_name should be replaced by the name of your module):
drupal_add_js(drupal_get_path('module', 'module_name').'/swfupload/swfupload.js');
drupal_add_js(drupal_get_path('module', 'module_name').'/swfupload/swfupload.cookies.js');The plugin is initialized and controlled by Javascript, so we need to create one additional js file. I will assume it is called module_swf.js and is located at the root of your module file hierarchy, but again, you can call and place it however you like. A very basic example file as an example is attached at the bottom of this post. This file needs to be included with the following line in the hook_form() function (where module_name should be replaced by the name of your module):
drupal_add_js(drupal_get_path('module', 'module_name').'/module_swf.js');This file should set the window.onload handler to initialize the SWFUpload plugin. To do this, include the following lines (where module_name should be replaced by the name of your module):
var swfu;
window.onload = function () {
swfu = new SWFUpload({
flash_url : '/drupal/modules/module_name/swfupload/swfupload_f9.swf',
...
other lines
});
}This initializes a global SWFUpload object called swfu, which will be used by various functions later on. Note that I mentioned other lines need to be added when initializing the SWFUpload object. These will be discussed later on.
Note als that this method overrides (or gets overridden by) any other window.onload handlers that are set. So if you need to call other Javascript code on window loading, you should wrap all these functions together (which I won't describe here).
Step 2: Configuring the SWFUpload plugin
The SWFUpload plugin is quite flexible and can be tailored to your needs in a straight forwarded way. This is described on the SWFUpload documentation page: http://demo.swfupload.org/Documentation/. Only the configuration that is relevant to getting the file upload to work is discussed here.
One aspect which need to be set is where the file should be submitted to, which is the action parameter of the form. This can be obtained with the following Javascript line in the window.onload() function:
action_url = document.getElementById('node-form').actionThis value needs to be passed to the initialization of the SWFUpload object as the upload_url parameter.
Another essential parameter in initialization is file_post_name. In order to integrate with standard Drupal file upload handling, the uploaded file should be identified with an array called files['identifier'], while SWFUpload standard sets this identifier 'Filedata' (note that this setting is unrelated to the name of the actual file). For here I assume the identifier 'swf', so the file_post_name should be set to 'files[swf]'.
To recap, the module_swf.js file should now read (where module_name should be replaced by the name of your module):
var swfu;
window.onload = function () {
action_url = document.getElementById("node-form").action
swfu = new SWFUpload({
flash_url : '/drupal/modules/module_name/swfupload/swfupload_f9.swf',
upload_url : action_url,
file_post_name : 'files[swf]',
...
other lines
});
}Step 3: Integrating SWFUpload in your form
Now that we have SWFUpload loaded into our desired page, it's time to make the functionality available to the user. A simple scenario is assumed in which the user presses a button to bring up a file selection dialog, and the upload will start when it is closed.
HTML forms can have two types of buttons; simple buttons, which do nothing until they are programmed to do something, and submit buttons, which send the form data to the action URL and display the new webpage the server sends. SWFupload does its magic by sequentially submitting each file as POST data to the action URL. Therefore, the page may not be refreshed after submitting a file (at least not until all files are sent), so we should use a normal button rather than a submit button. Here we hit a snag. The Drupal documentation claims that it is possible to have standard buttons, but at least in the 5.x series the results is a submit button! Therefore, we need some Javascript magic to make this work.
Let's assume you created a form called 'upload' in your hook_form() function using standard Drupal functionality. Then, to create the button, add the following code:
$form['upload']['swf'] = array(
'#type' => 'button',
'#name' => 'swfupload_button',
'#value' => 'Send',
);Of course, you can use any value you want for the name and value, but I assume the values displayed above for the remainder of this post.
Now we have a submit button, which we will need to transform into an SWFUpload button using Javascript. To do this, we set the onclick handler for the button during page loading in our module_swf.js file with the following lines:
var swf_button = document.getElementById('edit-swf');
swf_button.onclick = function () {
swfu.selectFiles();
return false;
};By returning false with this function, form submitting is disabled and SWFUpload can silently process all files in the background. By calling the selectFiles() function on the global SWFUpload object (swfu), a file selection dialog is brought up.
Note that if you didn't call your button 'swf', the id of your button should be 'edit-your_name'.
Step 4: Creating the file upload procedure for SWFUpload
When the user has selected the files to upload, SWFUpload should start submitting them one by one. Again, SWFUpload provides a large toolkit for tailoring this precedure to your needs. Here only a very simple procedure is assumed to demonstrate what is needed, in which the closing of the selection window starts the upload procedure without any user feedback.
The upload procedure is started by setting the file_dialog_complete_handler during initialization to a function we create for starting the upload on the SWFUpload object (swfu). This callback is fired when the file selection dialog. We will call our function beginSending() (but it can take any name you like). By design, it takes two parameters; the number of files selected and the total number of files queued. The initialization code of thje SWFUpload object thus becomes (where module_name should be replaced by the name of your module):
swfu = new SWFUpload({
flash_url : '/drupal/modules/module_name/swfupload/swfupload_f9.swf',
upload_url : action_url,
file_post_name : 'files[swf]',
file_dialog_complete_handler : filesSelected,
...
other lines
});This method should set several post parameters. This can be done using the addPostParam() function of the SWFUpload object. If you want to send every form parameter with your submission, you can use a simple loop over all the form elements, like the following one:
elements = document.getElementById("node-form").elements
for(i=0; i < elements.length; i++) {
name = elements[i].name
if (name) {
value = '';
switch(elements[i].type) {
case "select" :
value = elements[i].options[elements[i].selectedIndex].value
break;
case "radio":
case "checkbox":
value = elements[i].checked ? 1: 0;
break;
default:
value = elements[i].value
break;
}
swfu.addPostParam(name, value);
}
}Next, we need to add the 'op' parameter, which normally indicates the value of the submit button pressed. Since we don't actually press a submit button, we have to manually inject the value. I assume the name 'SWFSubmit' here (but you can nam it whatever you want).
swfu.addPostParam('op', 'SWFSubmit');To start the upload, you can simply call the startUpload() function of the SWFUpload object:
swfu.startUpload();Note that this just uploads the first file in the queue. To process the entire queue you need a callback for the finishing of uploaded file. Also, you probably want some error handling and such. See the attached module_swf.js file for some basic examples on that.
Step 5: Process the uploaded files
Now that we can sequentially submit files, we need configure Drupal to process them. Processing files is normally done using the Drupal hook_submit() function. However, this code is only executed when a submit button is actually pressed, which we don't. So we have to trick Drupal into thinking that we did, by setting the $form_submitted value to TRUE in the hook_validate() function (where module_name should be replaced by the name of your module):
function module_name_validate($form_id, $form_values) {
if (array_key_exists('op', $_POST) && ($_POST['op'] == 'SWFSubmit')) {
global $form_submitted;
$form_submitted = TRUE;
}
}Now, in our hook_submit() function, we can use the 'op' parameter in a standard way. Moreover, we can use the standard Drupal file_check_upload() function to handle the uploaded file, with the name we have chosen for the file_post_name in step 2. Remember that we just need to handle a single file, since each file is sequentially submitted. In our case, the name chosen was 'swf', so the uploaded file is accessible with:
$file = file_check_upload('swf');Another problem is that the session cookie is sometimes lost with SWFUpload, due to a bug in the Flash player. The result is that Drupal regards the submission as coming from an anonymous user instead of the logged in user. Depending on the permissions settings for your website, this can result in a rejection of the submitted content.
We can work around this problem by sending the session cookie along with the post parameters, and then reinstantiating the session manually. Remember the cookie plugin we included in step 1? This will take care of sending the cookies for the page we're on, so that step is already covered for us. In the module code however, we need to add (or append to) the hook_init() function with the following code (where module_name should be replaced by the name of your module):
function module_name_init() {
foreach (array_keys($_POST) as $key) {
if (substr($key, 0, 4) == "SESS") {
if(!isset($_COOKIE[$key])) {
$_COOKIE[$key] = $_POST[$key];
}
session_id($_POST[$key]);
session_decode(sess_read($_POST[$key]));
}
}
}Final thoughts
As I already mentioned, this post merely reflects my own experiences integrating SWFUpload in Drupal. I think that these instructions explain everything that is needed to get it working. However, if you feel something is missing or incorrect, please be so kind to share it here.
I think this method doesn't open up any security holes, but please be advised that I'm not an expert on either Drupal, PHP, webservers or security.
There are several other issues with file uploading not covered by this post. The configuration of the server will largely dicatate what you can't and cannot do. For example, SWFUpload lets you limit the size of files to upload, but the server has its own settings which you can not override. Please see the documentation of your server for that.
References
- SWFUpload Documentation: http://demo.swfupload.org/Documentation/index.html
- Download SWFUpload: http://code.google.com/p/swfupload/
- Drupal 5.x Forms API: http://api.drupal.org/api/file/developer/topics/forms_api.html/5
- The Cookie workaround: http://blog.ascaniocolonna.com/?p=23
Example module_swf.js
var swfu;
var action_url;
var continue_upload = true;
function beginSending() {
elements = document.getElementById('node-form').elements
form_values = new Array()
for(i=0; i < elements.length; i++) {
name = elements[i].name
if (name) {
value = '';
switch(elements[i].type) {
case 'select' :
value = elements[i].options[elements[i].selectedIndex].value
break;
case 'radio':
case 'checkbox':
value = elements[i].checked ? 1: 0;
break;
default:
value = elements[i].value
break;
}
swfu.addPostParam(name, value_;
}
}
swfu.addPostParam('op', 'SWFSubmit');
continue_upload = true;
swfu.startUpload();
}
function handleFileUploaded(file_object) {
if (swfu.getStats().files_queued > 0) {
if (continue_upload) {
swfu.startUpload();
}
} else {
window.location.href = action;
}
}
function handleError(file_object, error_code, message) {
if (message != 302) {
alert('Server refused file ' + file_object.name + ' with error message "' + message + '"');
continue_upload = false;
}
}
window.onload = function () {
action_url = document.getElementById('node-form').action
swfu = new SWFUpload({
upload_url : action_url,
file_post_name : 'files[swf]',
flash_url : '/drupal/modules/mymodule/swfupload/swfupload_f9.swf',
file_dialog_complete_handler : filesSelected,
upload_complete_handler : handleFileUploaded,
upload_error_handler : handleError,
});
var swf_button = getElementById('edit-swf');
swf_button.onclick = function () {
swfu.selectFiles();
return false;
};
};
Comments
Exellent job
Your guide helps me solve the cookie problem in my project.
Many thanks to you.
Editing images
I just installed this module and I was expecting to be able to crop/edit the images i upload. Can you, please, tell me what else should I install in order to have the edit image options?
Thanks.
I've tried to do with drupal
I've tried to do with drupal 7 but I haven't been able to make it work. The file don't upload
$_FILES is null