Adding New Fields to File Uploads

Mar
31

Handling file uploads have always been a bit of a challenge with Drupal. Uploads work, so long as you want them to work exactly the way they have been implemented through CCK or the upload module. Changing this default functionality, in versions going back to 4.6, has always been something of a pain, involving hacks to core or building one-off upload fields to handle things the way you want.

The introduction of CCK has made this a bit easier, through widgets, but there are still challenges. We built a site recently that allowed users to upload multiple images into a photo gallery, and it needed to collect more information about each than the fields collected through imagefield (in this case, besides a file and title, we needed an attribution, a description, a caption, a date, and some other stuff to help the client keep track of things).

Adding fields to a CCK upload field is surprising easy, but it is a little tricky figuring it out the first time. Here is what we did to make it all work.

First off, we decided to use the imagefield CCK widget as a basis to get started. Imagefield has it's own form handler (imagefield_widget) and a textfield for uploading the document and storing a description, along with AJAX controls for reordering files. So it has all the basic elements we needed to handle the additional fields.

The challenge is, when the form is generated, there is only one element for each imagefield and no way to add our own fields to the form. So, a simple call to hook_form_alter would not allow us to alter the form elements themselves. We got around this issue using custom #process and #element_function callbacks. Each callback expands our original form element after other parts of the form_alter have fired, giving us a way to change the final construction of the element before the form is rendered.

Let's say you want to add custom field called 'color'. Our callback would add a field called 'color' as a textfield people can fill out. For imagefield there are two important callbacks - filefield_widget_process and imagefield_widget_widget_process. We use these callbacks to add original handlers for rendering fields and validating form submissions.

Assume there is a custom node type called 'Color Image' and it contains an imagefield 'field_image'. In this example, the call to form_alter would look like:

<?php
function image_color_form_alter(&$form, $form_state, $form_id) {
  if (
$form_id == "color_image_node_form") {
   
// each field can have multiple values, we need to add custom process function to every upload field
   
foreach (element_children($form['field_image']) as $key) {
      if (
$form['field_image'][$key]['#type'] == 'imagefield_widget') {
       
// we have to include original process functions otherwise field wont be build correctly
       
$form['field_image'][$key]['#process'] = array('filefield_widget_process', 'imagefield_widget_widget_process', 'image_color_widget_process');
       
// same with validation function
       
$form['field_image'][$key]['#element_validate'] = array('filefield_widget_validate', 'imagefield_widget_validate', 'image_color_widget_validate');
      }
    }
  }
}
?>

Notice we are not trying to change the structure of the form element, we are simply adding validation and processing hooks to get the form to look the way we want. The processing function is what will render the additional fields we need to process file uploads. It will be fired before the form is rendered and will allow us to add new fields to the data array.

In the case of the 'field_color' example, it would look like this:

<?php
function image_color_widget_process($element, $edit, &$form_state, $form) {
 
$file = $element['#value'];

 
$element['data']['color'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Color'),
   
'#value' => $file['fid'] ? $file['data']['color'] : ''
 
);

  return
$element;
}
?>

Pretty simple. All we are doing is delaying the processing of the form element until just before the form is rendered, to ensure we have all the fields we may need (and also, to ensure that additional upload fields will be rendered properly through the AHAH interface).

In our validation function, we set our own rules for verifying form submissions.

<?php
function image_color_widget_validate($element, &$form_state) {
  if (
$element['fid']['#value']) {
    if (
$element['data']['color']['#value'] && is_numeric($element['data']['color']['#value'])) {
     
form_set_error(implode('][', $element['data']['color']['#array_parents']), t('Color cannot be number'));
    }
  }
}
?>

Notice that we are accessing information through the element object, not a node or form array. This is because the function will be used to validate individual form elements when the form is submitted.

Also notice we do not have to use a submit handler to store the data associated with the element. We could put the data in another table if we wanted, but with imagefield there is no need. All of the data associated with the uploaded file is stored as a serialized array, and becomes available to Drupal anytime the node is loaded. You can call the color value any time you are displaying the node, as seen below:

<?php
  $node
->field_image[0]['data']['color'];
?>

And that is it! I wish they would have had something this easy back in the days of Flexinode...

10 Comments

Is this meant for Drupal 6?

Is this meant for Drupal 6? I'd really love to get this to work in Drupal 5. Is there a way?

This is written in very

This is written in very technical language.

All I want to do is add a Description field to the core upload module, so I can extract this description from the mysql database using php script.

CCK 3 will allow us to do

CCK 3 will allow us to do exactly this by making use of multigroups:
CCK 3: Introducing the multigroup module
.

Hmmm... works great until

Hmmm... works great until you choose 'Add another'; all extra data is lost at that point. Have I missed something that is breaking my AHAH?

Hi, I created a module

Hi,

I created a module following the steps of http://hutten.org/bill/extjs/2010/02/drupal-adding-metadata-to-file.html that's a variation of the code in this site and I have the same problem.

The new extra metadata is sown when I create my content but it disapears when I click on "Add more".

I would like to have a filefield with extra information like text or checkbox.

Did you solve the problem?

Hi Bill, As per Wim's comment

Hi Bill,

As per Wim's comment above, I really would recommend for a Drupal 6 site going down the CCK3 multigroup field method. This allows cck fields to be grouped and for that group to be added multiple times (like a regular multiple value cck field).

Hope this helps.

Well, I am going to give it a

Well, I am going to give it a try... I have hesitated so far because there is not an official release version yet, and I have heard conflicting words about how stable/production-ready it is.

One way to find out I guess...

I take it your experience has been OK?

I haven't had issues with

I haven't had issues with filefields, imagefields & core cck3 fields. Good luck :)

Not sure if the approach

Not sure if the approach above works with AJAX, but I had used another approach in the past:

function filefield_node_elements() {
  return array(
    'imagefield_widget' => array(
      '#process' => 'filefield_node_widget_process',
    ),
    'filefield_widget' => array(
      '#process' => 'filefield_node_widget_process',
    ),
  );
}

This is also how popular modules like filefield_sources and even imagefield do extend the file/image widget.

Using hook_elements() works with AJAX, add another.

Maybe an alternative - if you want to add this information to all images.

Note: This approach will have problems upgrading to D7 as the data array is no longer there in Drupal 7.

The code would need to save the value / array in an external / own module table then.

Or back to CCK3 + MultiGroup field :-).

Best Wishes,

Fabian

In order to make the "Add

In order to make the "Add another item" button work, I needed to add a special case to the hook_form_alter function. The AHAH process uses a different form_id than you'd expect.

Change:

if ($form_id == "color_image_node_form")

to:

if ($form_id == "color_image_node_form" || $form_id == 'content_add_more_js')

Other than that, awesome article. Very simple...
criznach

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options