Drupal: File Uploads

Like most Drupal tasks, uploading a file is dirt simple. But only if you already know how. Lucky for you, I do.

Step one is to put a Drupal form element of type "file" one your form with which to select your file. Here's an example of a simple form containing the element.

  // example code assumes Drupal 6.0
  function mymodule_form($form_state) {
    $form = array(); // reset array
    $form['#attributes'] = array('enctype' => "multipart/form-data");
       
    $form['datafile'] = array(
    '#type' => 'file',
    '#title' => t('Upload data'),
    '#size' => 48,
    '#description' => t('A CSV in the requisite format'),
    );
   
    $form['submit'] = array(
    '#type' => 'submit',
    '#value' =>  t('Submit'),
    '#id' => 'upload-file',
    );

    return $form;
  }

The "datafile" key is just the name I gave it. The file element is a slightly modified version of the standard HTML file form element. It generally acts like the HTML version from the viewpoint of the end user, but what happens on the back end is a bit different. This is obvious once the element is submitted.

  function mymodule_form_submit($form, &$form_state) {

    // set datafile upload variables
    $source = 'datafile';
    $validators = array( 'file_validate_extensions' => array( 'csv' ) );
    $dest = file_directory_temp();
   
    // upload datafile
    $file = file_save_upload($source,$validators,$dest, false);
    if($file){
        drupal_set_message("File successfully uploaded as " . $file->filepath);
    }else{
        drupal_set_message('No file uploaded.');
    }
       
    ....
       
    //do something with $file
    if($file){
      $aFile = mymodule_process_file($file);
    }
   
    ....
}

The file_save_upload function takes three variables. The "source" is simply the name you gave to your file element. A key thing to note here is that the file element I created here is an object that has no readable value or purpose, other than to pass the file to be uploaded to the function that will do so. You cannot, for instance, determine what file was selected prior to actually attempting to upload it. "$validators" refers to an array of callback functions. The simplest use, as I have done, is to call out to the file_validate_extensions function to validate your extension. Next, the "$dest" variable (target folder) defaults to the site upload directory but you can change it, as I did here, to just about anything. Finally, the boolean at the end determines whether or not files with the same name are copied over ("true") or if the new file is renamed to "file_#" ("false").

The return value, the first chance you have of reading anything, is a Drupal file object if it was uploaded, or the boolean "false", otherwise. The $file object has the following keys:

  • filename e.g. "data.csv"
  • filepath e.g. "/tmp/data.csv" (where you intend to write the file)
  • filemime e.g. "text/csv"
  • source e.g "datafile" (form element name)
  • destination e.g. "/tmp/data.csv" (possibly renamed with safe extention)
  • filesize e.g. "748" (bytes)
  • uid e.g. "1" (the uploader)
  • status e.g. "1" (was the file recorded in the DB)
  • timestamp e.g. "1277515165"
  • fid "234" (Unique ID in files table)

To actually access and use the file, you would do something like this:

function mymodule_process_file($file){
    $sFloc = $file->filepath;
    drupal_set_message("Reading data file $file->filename ");
    // read in csv file
    if (($handle = fopen($sFloc, "r")) !== FALSE) { //Open the File.
        // Set the parent multidimensional array key to 0.
        $iKey = 0;
        while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
            // Count the total keys in the row.
            $iCount = count($data);
            // Populate the multidimensional array.
            for ($iElement=0;$iElement<$iCount;$iElement++)
            {
                $aFile[$iKey][$iElement] = $data[$iElement];
            }
            $iKey++;
        }
        // Close the File.
        fclose($handle);
    }
   
    //delete $file
    if(file_delete($sFloc)){
        //  delete from files table
        db_query("DELETE FROM {files} WHERE  filepath = '%s'", $sFloc);
        drupal_set_message($sFloc . " deleted");
    }
   
    return $aFile;
}

Here. I'm opening the file with fopen using the location passed by the object. For this example, I loop through the rows of the imported CSV file, assigned the values to an array. When I'm done with the file, I delete it with Drupal's file_delete which removes the file from the server, and should remove it from the files table, but doesn't. I handle the table clean-up with a db_query and then, finally, return the data from my file in an array.

In the final analysis, it takes a form element, a save command, and file open to grab a file from the end-users desktop and start working with it. Fairly elegant, once you get the hang of it.

Tags: