With a form of the following type (assmuming enctype is set correctly):

$form['foo'] = array(
  '#type' => 'fieldset',
  '#title' => 'foo',
  '#tree' => TRUE,    
);
$form['foo']['bar'] = array(
  '#type' => 'file',
  '#title' => 'bar',
}

There is no way to use form_check_upload or file_save_upload as those functions only check $_FILES['edit']['name'][$source] but the form will generate $_FILES['edit']['name']['foo']['bar'].

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

magico’s picture

Title: file_check_upload and file_save_upload don't work when #tree is TRUE » When #tree is TRUE causes problems with file_check_upload() and file_save_upload()

Anyone else had this problem? Need confirmation, because it seems strange to the presence of this bug.

Thanks.

mr700’s picture

I can confirm this, had to use flat fieldset for files.

jcastr1’s picture

I also had this problem. I had to use markup tags in the form to display fields in a fieldset.

explained here http://drupal.org/node/83506

mikeryan’s picture

Title: When #tree is TRUE causes problems with file_check_upload() and file_save_upload() » file_save_upload() incompatible with #tree = TRUE
Version: 4.7.3 » 6.3

This issue remains in Drupal 6 - is there a reason not to support file_save_upload() in forms with #tree=TRUE, other than no one has offered a patch yet?

Thanks.

mikeryan’s picture

Status: Active » Closed (fixed)

Actually, never mind... Looking closer at how $_FILES is arranged I see that it does work, you just have to pass the fieldset name in $source.

Thanks.

mistresskim’s picture

Title: file_save_upload() incompatible with #tree = TRUE » $source parameter in file_save_upload() when field is in a fieldset
Version: 6.3 » 6.4
Category: bug » support
Status: Closed (fixed) » Active

Can anyone confirm this is working for them? And if so, how they specified the $source parameter in file_save_upload(). I have a form with two upload fields as follows:

$form['photos']['photo01'] = array(
'#type' => 'file',
'#title' => t('Photo 1'),

);

$form['photo02'] = array(
'#type' => 'file',
'#title' => t('Photo 2'),

);

I put the second in just to make sure the form was okay.

$file = file_save_upload('photo01', $validators, $dest, FALSE) produces a $file object with just "0" but
$file = file_save_upload('photo02', $validators, $dest, FALSE) works fine.

Yet in system.admin.inc, which has an upload field for the site logo in a fieldset, $file = file_save_upload('logo_upload', array('file_validate_is_image' => array())) also works.

Is there something else I need to declare so the function knows the field name is inside a fieldset?

ainigma32’s picture

Status: Active » Fixed

Like mikeryan said; if you use a file field inside a field setting you use the fieldset name in stead of the file field name.
So in your case that would mean $file = file_save_upload('photos', $validators, $dest, FALSE) would get you photo1.

You will run into problems if you try to use more than one file field inside a fieldset. In that case the last file field is returned.

- Arie

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.

hanoii’s picture

Title: $source parameter in file_save_upload() when field is in a fieldset » $source parameter in file_save_upload() not work as expected when field is in a fieldset with #tree set to TRUE
Version: 6.4 » 6.12
Category: support » bug
Status: Closed (fixed) » Active

I just got here while porting a module which uses a file field inside a fieldset with #tree set to true. Not sure if this issue should be closed.

- What happens if we have more than one file field inside a fieldset with #tree set to true? Probably it won't work, thus it's a bug, unless it's a known issue, which should be documented somewhere in the API, but at least leaving it open leaves some probability of looking into this issue.

kirikintha’s picture

This is a Q&D on how we did it with system_settings_form on Drupal 6.12

In ours we did this with our fieldset:


//function yourform

    $form['exhibitor']      = array(
        '#type'             => 'fieldset',
        '#title'            => t('Exhibitor Settings'),
        '#collapsible'      => true,
        '#collapsed'        => false
    );


    $file = variable_get('event_registration_exhibitor_floor_plan','');
    $form['#attributes'] = array('enctype' => "multipart/form-data");
    $form['exhibitor']['event_registration_exhibitor_floor_plan']   = array(
        '#type'             => 'file',
        '#title'            => t('Floor Plan'),
        '#description'      => t('Upload the floorplan for the event this year. If you are changing the floorplan you will need <a href="taylor@liquidfire.com">LiquidFire</a> to make the floorplan clickable.'),
    );
    
    $form['exhibitor']['floorplan']['preview'] = array(
        '#value' => theme('image',$file->filepath)
    );

//rest of form
return system_settings_form($form);


//then in function yourform_validate

        $dest = variable_get('file_directory_path', '').'/floorplans';
    
        // set error is file was not uploaded
        if ( $file = file_save_upload( 'event_registration_exhibitor_floor_plan', array(), $dest, false ) ) {
            drupal_set_message('Success! Your floorplan has been uploaded.','status');
        } else {
            drupal_set_message('Sorry, your floorplan has not been uploaded. Please try again.','error');
            return;
        }
       
        // set files to form_state, to process when form is submitted
        $form_state['values']['event_registration_exhibitor_floor_plan'] = $file;  

Dmitriy.trt’s picture

Version: 6.12 » 6.15
Status: Active » Needs review
FileSize
4.49 KB

Patch was made from Drupal 6.15, it adds support for #tree and file form elements inside tree. You can use it just like you set error in validation function for elements inside tree:

  //Form definition:
  $form = array(
    '#attributes' => array(
      'enctype' => 'multipart/form-data',
    ),

    'fisrt' => array(
      '#tree' => TRUE,
      'second' => array(
        'my-upload' => array(
          '#type' => 'file',
          '#title' => t('Upload inside tree'),
        ),
      ),
    )
  );
  
  //Validation/submission:
  $file = file_save_upload('first][second][my-upload');

I don't think it can break additional modules, because before it files with #tree didn't work at all.

Status: Needs review » Needs work

The last submitted patch, drupal-6.15-tree-file-fix.patch, failed testing.

Dmitriy.trt’s picture

New patch, made from CVS checkout of DRUPAL-6

Dmitriy.trt’s picture

Status: Needs work » Needs review
FileSize
4.97 KB

Sorry, didn't change status...

Status: Needs review » Needs work

The last submitted patch, drupal-6.15-tree-file-fix-cvs.patch, failed testing.

Dmitriy.trt’s picture

Version: 6.15 » 6.x-dev
Status: Needs work » Needs review
Dmitriy.trt’s picture

Dmitriy.trt’s picture

New patch, fixed bug with filename

Albert Volkman’s picture

Subscribing

Dmitriy.trt’s picture

Does anyone know what is wrong with testing? Patch has status "Test request sent" since 02/26/2010 !!!

kenorb’s picture

broon’s picture

I encountered the same problem.
I wrote a module for a client which defines a new custom content type (due to various reasons CCK is not suitable). This content type also includes a number of images which (for workflow reasons) are contained in a fieldset named 'images'. When adding multiple images (user may add additional images by clicking AHAH button 'add another image') Drupal only saves the last entered image in fieldset.

However, by just giving the form element a proper name, I was able to get around this behaviour:

function mymodule_form(&$node, $form_state) {
...
  $form['images'] = array(
		'#type' => 'fieldset',
		'#title' =>  t('Images'),
		'#weight' => 0
  );
...
  for ($delta = 0; $delta < $images_count; $delta++) 
  {
    $form['images']['images_wrapper']['image'][$delta] = _mymodule_images_form($delta, $images_list[$delta]);
  }
}

function _mymodule_images_form($delta, $value = array(), $votes = 0) {
...
  $form[$delta]['image_upload'] = array(
		'#type' => 'file',
		'#title' => t('Upload image'),
    '#parents' => array('images', $delta, 'image_upload'),
		'#description' => t('Select an image file (.jpg, .gif, .png)'),
    '#name' => 'files[images]['.$delta.'][image_upload]'
  );
...
}

Upon saving an image I can recall these array paths:

  if ($file = file_save_upload('images]['.$delta.'][image_uploads', $validators, file_directory_path() . '/uploads/images')) {

While this works for me, I'm not sure if it is the "Drupal" way or could break anything else. But as I understand, Dmitriy.trt's patch tries to shift this into an automated service (the patch didn't work for me, though).

Regards,
Paul

Edit: Seems I got something messed up with the patches. When it stopped working I tried to investigate further and am now pretty sure what was going on.
When applying the patch, it didn't work for me which was probably the fault of my modules code. Anyhow after removing the patch I wrote my "workaround" above and it worked for me. However, my FTP programme failed on uploading the original file.inc so my code only works if I apply the patch to file.inc. I didn't patch form.inc but used my code instead.
Anyway, the support of file uploads within fieldsets should be provided by core.

WesleyWex’s picture

So, it has been almost four years since this bug was first reported. This is just disappointing.

Status: Needs review » Needs work

The last submitted patch, drupal-6.15-tree-file-fix-cvs-2.patch, failed testing.

madmanmax’s picture

Still counting... issue is not fixed. Any updates on the patch?

Wbird’s picture

same problem, give up...

qrohlf’s picture

I'm attempting to use the fix from #22, but having trouble with it. For now, I am hardcoding the #name and #parents values to be similar to the ones shown above, once I get it working I'll change it to a dynamic name.
My form gets declared like this:

$form['folders'][$projectfolder->name][$graphic->filepath.'_upload'] = array(
          '#type' => 'file',
          '#field_prefix' => 'Upload: ',
          '#description' => 'Upload a new branded graphic to replace the stock graphic',
          '#name' => 'files[images][delta][image_upload]', //TODO
          '#parents' => array('images', 'delta', 'image_upload'), //TODO
);

And I try to retrieve the file like this:

$file = file_save_upload('images][delta][image_upload', array(), file_directory_path() . '/uploads/images');

When I pick an image to upload and hit submit, the resulting [files] array looks like this:

[files] => Array
                (
                    [images] => Array
                        (
                            [delta] => Array
                                (
                                    [image_upload] => icon.png
                                )

                        )

                )

I've tried lots of variations on this, but file_save_upload always seems to return 0. What am I doing wrong?

If more detail on the issue would be helpful, check my post over here: http://drupal.stackexchange.com/questions/6059/uploading-from-within-fie...

RdeBoer’s picture

Finding the same in D7....
The $_FILES array does not support nested form elements (i.e. #tree => TRUE on the parent form element).
So each file element on the form must have a unique name, which is a pain when your form can be dynamically extended with more rows/fieldsets via the user pressing "Add another".

Part of the problem (in D7) may be here, in form.inc.
Note the comment in the code: "we do not support nested file names".

function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
    .......
    if ($element['#type'] == 'file') {
      // To make it easier to handle $_FILES in file.inc, we place all
      // file fields in the 'files' array. Also, we do not support
      // nested file names.
      $element['#name'] = 'files[' . $element['#name'] . ']';
    }
     .......

A potentially clue to a fix is somehow manipulating $element['#name'] external to the above function, matching it up with the way the $_FILES array is indexed, very much along the lines of what people have had a go at above.
All in all pretty messy.

RdeBoer’s picture

So here is what worked for me in D7.

I have a file selection form element (#type => 'file') inside a fieldset which in itself lives in an outer fieldset on which I've set #tree => TRUE, thus causing the same problem everyone has been having above.
In fact in my case the outer fieldset has 'Add another' and 'Remove last' buttons to allow the user to dynamically add more or remove inner fieldsets. And each of the inner fieldsets, in my case has not 1 but 3 file upload selectors, as '#multiple' appears not to be supported.

Anyway, the code below handles this and similar cases. The great thing about this code is that you do not have to patch core.

You need to:

  • Use the #name attribute on the file form element to set a unique name of the form files[...]. What you put for the ... is truly arbitrary, as long as it is unique. In my case, because my outer fieldset is called 'products' and my inner fieldsets can grow from 1..many and I have up to 3 files (attachments) I went for files[products-$row-attachment$i-fid], just to follow the hierarchy of the form. But really I could have just gone for a, b, c, d, e...
  • Insert the following code in your form _validate function:
  // Workaround for missing core function, see https://drupal.org/node/83698
  // Find all form file elements that correspond to files submitted for upload.
  foreach ($_FILES['files']['name'] as $form_field_name => $filename) {
    if ($filename) {
      // Find the form file element that corresponding to $form_field_name.
      foreach (element_children($form['products']) as $child_key) {
        foreach (element_children($form['products'][$child_key]) as $grand_child_key) {
          if (isset($form['products'][$child_key][$grand_child_key]['fid'])) {
            $element = $form['products'][$child_key][$grand_child_key]['fid'];
            if ($element['#name'] == "files[$form_field_name]") {
              if ($file = file_save_upload($form_field_name)) {
                // Put the file in the form state so we can save it on submit.
                $form_state['values']['products'][$child_key][$grand_child_key]['fid'] = $file->fid;
              }
              else {
                form_error($element, t('File %name could not be uploaded.', array('%name' => $filename)));
              }
              break;
            }
          }
        }
      }
    }
  }

You'll need to adjust it a little. For example your outer fieldset may not be called 'products'. Also, you may not have the extra 'fid', in which case just drop it.

Now it is clear why you may use any unique identifier for #name: it simply goes through all names in the $_FILES array and then traverses the form to find the matching form element. Then it loads the selected file. Or if there's an error (wrong extension etc.) it highlights in red the offending file upload widget.

Then in your _submit handler you pick the fid or fids from $form_state['values'] and save them via variable_set() or whatever mechanism you wish to use.

Final note. As the file upload widget does not seem to allow a #default_value I display the selected value in the #description (via file_load($fid)) and use a check box next to the file upload widget to remove an existing selection. This then works with the following code, to be inserted in _validate just before the code above:

  $config = variable_get('my_module_config', array());
  foreach ($config['products'] as $key => $product) {
    for ($i = 0; $i < 3; $i++) {
      if (empty($form_state['values']['products'][$key]["attachment$i"]['remove'])) {
        $form_state['values']['products'][$key]["attachment$i"]['fid'] = $config['products'][$key]["attachment$i"]['fid'];
      }
      unset($form_state['values']['products'][$key]["attachment$i"]['remove']);
    }
  }

Status: Needs work » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.

sreenivasparuchuri’s picture

I faced the same problem in drupal 8 which i fixed by using

'#name' => "files[upload_image]"

Below is the example


$form['settings']['logo']["upload_image"] = [
        '#type' => 'file',
        '#default_value' => '',
        '#name' => "files[upload_image]",
      ];
handkerchief’s picture

@sreenivasparuchuri Thank you so much for this!