Actions

Creating a file uploader in your component

From Joomla! Documentation

Revision as of 15:25, 13 December 2009 by Hasse.bjork (Talk | contribs)

Contents

Creating a file uploader in your component: Method 1, SWFUpload

Part 1: Introduction

SWFUpload is a free open source multiple file uploader available for download from http://swfupload.org/

A demo of swfupload is available here: http://demo.swfupload.org/v220/simpledemo/index.php

The standard way to upload a file is with a HTML form. This works great if you are uploading one file, but browsers do not let you select and upload more than one file at a time.

The most common way of selecting multiple files for upload is to use flash and javascript. The flash part of the script opens up a select file dialog where multiple files can be selected, and the flash/javascript sends each file, and recieves the response from the server.

The most popular scripts used to do this are SWFUpload and Fancy upload (http://digitarald.de/project/fancyupload/). The flash uploader in Joomla's media manager is based on Fancy Upload version 1.0. As Fancy upload 2.0 and 3.0 use mootools 1.2, and Joomla 1.5 uses mootools 1.11, we can not use Fancy Upload 2.0/3.0 in the admin pages of Joomla. Fancy Upload 1.0 could be used, but it is not updated or supported anymore.

SWFUpload does not use Mootools or Jquery. Because of this it will likely work on any page with Mootools 1.11 (Joomla 1.5), or Mootools 1.2 (Joomla 1.6). Therefore we will be using SWFUpload in this article.

Download ths latest version of SWFUpload from the download page (http://code.google.com/p/swfupload/). Download the SWFUpload v2.2.0.1 Samples.zip file.

Inside the file there will be a demos folder, and inside that a simpledemo folder. This is the example we are going to integrate into a Joomla component.

This tutorial assumes you have followed the http://docs.joomla.org/Developing_a_Model-View-Controller_Component_-_Part_1 tutorial and are familiar with Joomla's MVC structure.



Part 2: Adding The External Javascript

We need to add in the external links in the head of the document, put some javascript into the head of the doc, and put some HTML in our body.

If you look at the simpledemo/index.php page you will see links to 5 files in the head of the page.

Make a folder within the root of your components folder called swfupload, copy the ../css/default.css, ../swfupload/swfupload.js, js/swfupload.queue.js, js/fileprogress.js and js/handlers.js files into this folder.

We also need to copy the ../swfupload/swfupload.swf and images/TestImageNoText_65x29.png files into this folder.

Within your view.html.php file add in the following code:

//get the hosts name
jimport('joomla.environment.uri' );
$host = JURI::root();
 
//add the links to the external files into the head of the webpage (note the 'administrator' in the path, which is not nescessary if you are in the frontend)
$document =& JFactory::getDocument();
$document->addScript($host.'administrator/components/com_mycomponent/swfupload/swfupload.js');
$document->addScript($host.'administrator/components/com_mycomponent/swfupload/swfupload.queue.js');
$document->addScript($host.'administrator/components/com_mycomponent/swfupload/fileprogress.js');
$document->addScript($host.'administrator/components/com_mycomponent/swfupload/handlers.js');
$document->addStyleSheet($host.'administrator/components/com_mycomponent/swfupload/default.css');

That is the external files part done. We still have to add in the head javascript, and the body html.



Part 3: Adding The Head Javascript

Head javascript part: The javascript below is almost the same as the javascript from the simple demo, the few modifications are commented. It is nescessary to read through it and change the parts (such as task) that are relevant to your component.

//when we send the files for upload, we have to tell Joomla our session, or we will get logged out 
$session = & JFactory::getSession();
 
$swfUploadHeadJs ='
var swfu;
 
window.onload = function()
{
 
var settings = 
{
        //this is the path to the flash file, you need to put your components name into it
        flash_url : "'.$host.'administrator/components/com_mycomponent/swfupload/swfupload.swf",
 
        //we can not put any vars into the url for complicated reasons, but we can put them into the post...
        upload_url: "index.php",
        post_params: 
        {
                "option" : "com_mycomponent",
                "controller" : "mycontroller",
                "task" : "mytask",
                "id" : "'.$myItemObject->id.'",
                "'.$session->getName().'" : "'.$session->getId().'",
                "format" : "raw"
        }, 
        //you need to put the session and the "format raw" in there, the other ones are what you would normally put in the url
        file_size_limit : "5 MB",
        //client side file chacking is for usability only, you need to check server side for security
        file_types : "*.jpg;*.jpeg;*.gif;*.png",
        file_types_description : "All Files",
        file_upload_limit : 100,
        file_queue_limit : 100,
        custom_settings : 
        {
                progressTarget : "fsUploadProgress",
                cancelButtonId : "btnCancel"
        },
        debug: false,
 
        // Button settings
        button_image_url: "'.$host.'administrator/components/com_mycomponent/swfupload/images/TestImageNoText_65x29.png",
        button_width: "85",
        button_height: "29",
        button_placeholder_id: "spanButtonPlaceHolder",
        button_text: \'<span class="theFont">Choose Files</span>\',
        button_text_style: ".theFont { font-size: 13; }",
        button_text_left_padding: 5,
        button_text_top_padding: 5,
 
        // The event handler functions are defined in handlers.js
        file_queued_handler : fileQueued,
        file_queue_error_handler : fileQueueError,
        file_dialog_complete_handler : fileDialogComplete,
        upload_start_handler : uploadStart,
        upload_progress_handler : uploadProgress,
        upload_error_handler : uploadError,
        upload_success_handler : uploadSuccess,
        upload_complete_handler : uploadComplete,
        queue_complete_handler : queueComplete  // Queue plugin event
};
swfu = new SWFUpload(settings);
};
 
';
 
//add the javascript to the head of the html document
$document->addScriptDeclaration($swfUploadHeadJs);

If you want more info on what each paramater is, have a look at the swfupload docs: http://demo.swfupload.org/Documentation/



Part 4: Adding The Body HTML

We still have to add in our body HTML, in you tmpl/default.php file add in:

<div id="swfuploader">
        <form id="form1" action="index.php" method="post" enctype="multipart/form-data">
        <fieldset class="adminform">
 
                        <div class="fieldset flash" id="fsUploadProgress">
                        <span class="legend">Upload Queue</span>
                        </div>
                <div id="divStatus">0 Files Uploaded</div>
                        <div>
                                <span id="spanButtonPlaceHolder"></span>
                                <input id="btnCancel" type="button" value="Cancel All Uploads" onclick="swfu.cancelQueue();" disabled="disabled" style="margin-left: 2px; font-size: 8pt; height: 29px;" />
 
                        </div>
        </fieldset>
        </form>
</div>

If all has gone well, you will be able to upload files to your components script, although nothing will happen on the server as there is no PHP to recieve the image.

Part 5: Recieveing The File With PHP

It is worth noting that allowing users to upload files to the server is possibly the most dangerous thing to do in terms of security, and if someone with malicious intentions manages to upload a php file to your server, then they can easily take control of the site.

It is recommeneded you do some reading on file upload security. This is a good start:

http://www.scanit.be/uploads/php-file-upload.pdf

And there are many other good resources on the internet. It is important you understand how file upload security works, rather than copying and pasting some code that checks the MIME type, and assuming the script is safe.

Here is some example PHP that will check if the file is an image, and upload it. It is not an exhaustive example of security checking, but it is a good start:

//import joomlas filesystem functions, we will do all the filewriting with joomlas functions,
//so if the ftp layer is on, joomla will write with that, not the apache user, which might
//not have the correct permissions
jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');
 
//this is the name of the field in the html form, filedata is the default name for swfupload
//so we will leave it as that
$fieldName = 'Filedata';
 
//any errors the server registered on uploading
$fileError = $_FILES[$fieldName]['error'];
if ($fileError > 0) 
{
        switch ($fileError) 
        {
        case 1:
        echo JText::_( 'FILE TO LARGE THAN PHP INI ALLOWS' );
        return;
 
        case 2:
        echo JText::_( 'FILE TO LARGE THAN HTML FORM ALLOWS' );
        return;
 
        case 3:
        echo JText::_( 'ERROR PARTIAL UPLOAD' );
        return;
 
        case 4:
        echo JText::_( 'ERROR NO FILE' );
        return;
        }
}
 
//check for filesize
$fileSize = $_FILES[$fieldName]['size'];
if($fileSize > 2000000)
{
    echo JText::_( 'FILE BIGGER THAN 2MB' );
}
 
//check the file extension is ok
$fileName = $_FILES[$fieldName]['name'];
$uploadedFileNameParts = explode('.',$fileName);
$uploadedFileExtension = array_pop($uploadedFileNameParts);
 
$validFileExts = explode(',', 'jpeg,jpg,png,gif');
 
//assume the extension is false until we know its ok
$extOk = false;
 
//go through every ok extension, if the ok extension matches the file extension (case insensitive)
//then the file extension is ok
foreach($validFileExts as $key => $value)
{
        if( preg_match("/$value/i", $uploadedFileExtension ) )
        {
                $extOk = true;
        }
}
 
if ($extOk == false) 
{
        echo JText::_( 'INVALID EXTENSION' );
        return;
}
 
//the name of the file in PHP's temp directory that we are going to move to our folder
$fileTemp = $_FILES[$fieldName]['tmp_name'];
 
//for security purposes, we will also do a getimagesize on the temp file (before we have moved it 
//to the folder) to check the MIME type of the file, and whether it has a width and height
$imageinfo = getimagesize($fileTemp);
 
//we are going to define what file extensions/MIMEs are ok, and only let these ones in (whitelisting), rather than try to scan for bad
//types, where we might miss one (whitelisting is always better than blacklisting) 
$okMIMETypes = 'image/jpeg,image/pjpeg,image/png,imagex-png,image/gif';
$validFileTypes = explode(",", $okMIMETypes);           
 
//if the temp file does not have a width or a height, or it has a non ok MIME, return
if( !is_int($imageinfo[0]) || !is_int($imageinfo[1]) ||  !in_array($imageinfo['mime'], $validFileTypes) )
{
        echo JText::_( 'INVALID FILETYPE' );
        return;
}
 
//lose any special characters in the filename
$fileName = ereg_replace("[^A-Za-z0-9.]", "-", $fileName);
 
//always use constants when making file paths, to avoid the possibilty of remote file inclusion
$uploadPath = JPATH_SITE.DS.'images'.DS.'stories'.DS.$fileName;
 
if(!JFile::upload($fileTemp, $uploadPath)) 
{
        echo JText::_( 'ERROR MOVING FILE' );
        return;
}

If all want well the uploaded files will be in the images/stories folder.

Part 6: Flash plugin COOKIE bug on non-IE browsers

Also you should pay attention to Flash player cookie bug!

It is described here http://swfupload.org/forum/generaldiscussion/383

The Flash Player Plugin for FireFox, Opera and Safari (and probably other non-IE based browsers) has a bug which sends persistent cookies from IE to the upload URL instead of the cookies from the browser. Session only cookies from IE are not sent.

1) Any cookies from a non-IE browser (ie, authentication, login, etc) will not be sent with the file upload. Rather the cookies from IE will be sent. So, no cookies or the wrong cookies will be sent. In most cases this means sessions and cookie based authentication are lost when making an upload.

2) Cookies set by the upload script do get set, but only for Flash. The browser will not see them.

3) This bug affects Flash 8 and Flash 9 and all versions of SWFUpload

4) You cannot rely on cookies when using SWFUpload (or any Flash based upload tool). You must send the data you need from the cookies in another way. There are several threads regarding this issue in the forum and many of the demos show workarounds for restoring PHP sessions and some sample files show how to restore the cookies so Session and Authentication are restored in ASP.Net.

5) This could be considered a security issue but probably not severe enough to actually compromise any data. The cookies created in IE by Flash still have all the rules and restricts associated with cookies in any browser.

Solution is modify function _start() (libraries\joomla\session\session.php):

$sn = session_name();
 
if(isset($_COOKIE[$sn]) && isset($_POST[$sn])) 
   {
        $_COOKIE[$sn] = $_POST[$sn];
        session_id($_POST[$sn]);
   }
 
session_start();