When here is a manged_file form item (i.e. #type => 'managed_file'), and the form is modified using the system_settings_form($form) function, and there is content loaded into the form item, the following error will occur on form submit:
PHP Fatal error: Cannot unset string offsets in /Applications/MAMP/htdocs/mysite/includes/form.inc on line 2102

The exact same code works when system_settings_form is not called.

Comments

jesse.rosato’s picture

Seconded.

pivanov’s picture

The extra added buttons (upload and remove) seem to be causing this. They don't seem to have any values and one thing the submit handler (added by system_settings_form()) does is to remove any values buttons may have added to from $form_state['values'].

One possible way is to set the '#parents' attribute of each upload and remove button to an empty array. However, I don't know if this is a good idea.

In a submit handler (ran before the system_settings_form() one):

foreach($form_state['buttons'] as $delta => &$button) {
  if (preg_match('/managed_file_element_name_(upload|remove)_button/i', $button['#name'])) {
    $button['#parents'] = array();
  }
}
dealancer’s picture

Looks like managed_file don't work well with system_settings_form and should be processed manually. To fix this problem, see solution below

Here is my form:

  $form['files'] = array(
    '#type' => 'fieldset', 
    '#title' => t('Files'),
    '#collapsible' => TRUE, 
    '#collapsed' => FALSE,
  );

  $form['files']['custom_file_1'] = array(
    '#title' => t('File 1'),
    '#type' => 'managed_file',
    '#default_value' => variable_get('custom_file_1_fid', ''), // Use different variable name for the variable, so it won't be overridden by system submit handler
    '#upload_location' => 'public://custom/',
  );

  // Perform our custom submit before system submit
  $form['#submit'][] = 'custom_settings_form_submit';
 
  $form = system_settings_form($form);

and here is a custom submit:


function custom_settings_form_submit($form, &$form_state) {
  global $user;
  // Load the file via file.fid.
  $file = file_load($form_state['values']['custom_file_1']);
  if ($file) {
    // Change status to permanent.
    $file->status = FILE_STATUS_PERMANENT;
    // Save.
    file_save($file);
    // Save file to vatiable
    variable_set('custom_file_1_fid', $file->fid);
    // Record that the module (in this example, user module) is using the file. 
    file_usage_add($file, 'user', 'user', $user->uid);
    // Unset formstate value
    unset($form_state['values']['custom_file_1']); // make sure it is unset for system submit
  }
}

That's all.

However, we should except by processing this field by submit added in system_settings_form().

dealancer’s picture

Looks like managed_file doesn't work well with system_settings_form and should be processed manually. To fix this problem, see solution below

Here is my form:

  $form['files'] = array(
    '#type' => 'fieldset', 
    '#title' => t('Files'),
    '#collapsible' => TRUE, 
    '#collapsed' => FALSE,
  );

  $form['files']['custom_file_1'] = array(
    '#title' => t('File 1'),
    '#type' => 'managed_file',
    '#default_value' => variable_get('custom_file_1_fid', ''), // Use different variable name for the file field, so it won't be overridden by system submit handler
    '#upload_location' => 'public://custom/',
  );

  // Perform our custom submit before system submit
  $form['#submit'][] = 'custom_settings_form_submit';
 
  $form = system_settings_form($form);

and here is a custom submit:


function custom_settings_form_submit($form, &$form_state) {
  global $user;
  if (is_numeric($form_state['values']['custom_file_1'])) {
    // Load the file via file.fid.
    $file = file_load($form_state['values']['custom_file_1']);
    if ($file) {
      // Change status to permanent.
      $file->status = FILE_STATUS_PERMANENT;
      // Save.
      file_save($file);
      // Save file to variable.
      variable_set('custom_file_1_fid', $file->fid);
      // Record that the module (in this example, user module) is using the file. 
      file_usage_add($file, 'user', 'user', $user->uid);
      // Unset formstate value.
      unset($form_state['values']['custom_file_1']); // make sure it is unset before system submit
    }
  }
  else {
    // Delete file
    variable_set('bah_custom_file_1_fid', '');
  }
}

That's all.

However, we should except by processing this field by submit added in system_settings_form().

jaimealsilva’s picture

I have tried this on a theme's theme-settings.php:bla_form_system_theme_settings_alter() by adding "bla_settings_form_submit" to #submit but I get the error:

PHP Fatal error: Call to undefined function bla_settings_form_submit() in /.../includes/form.inc

How can I specify on which file to find the function ? Or where should I put the function ?

Thanks.

aviddv1’s picture

Have you tried using:

$form['#submit'][] = 'my_custom_form_submit';

inside your custom form function? That should tell Drupal to submit the form data to your _submit function.

I found I had to modify the example to get it to work.

- added a condition to check if the custom_file_1 value > 0
- if the condition fails then I attempt to load the file object from the saved variable, check if the object exists, manually force delete the file

function custom_settings_form_submit($form, &$form_state) {
  global $user;
  if (is_numeric($form_state['values']['custom_file_1']) && $form_state['values']['custom_file_1'] > 0) {
    // Load the file via file.fid.
    $file = file_load($form_state['values']['custom_file_1']);
    if ($file) {
      // Change status to permanent.
      $file->status = FILE_STATUS_PERMANENT;
      // Save.
      file_save($file);
      // Save file to variable.
      variable_set('custom_file_1_fid', $file->fid);
      // Record that the module (in this example, user module) is using the file. 
      file_usage_add($file, 'user', 'user', $user->uid);
      // Unset formstate value.
      unset($form_state['values']['custom_file_1']); // make sure it is unset before system submit
    }
  }
  else {
    // Load the file via file.fid.
    $file = file_load(variable_get('custom_file_1', ''));
    
    if ($file->fid)
    {
        // Delete the file and the usage record
        file_delete($file, TRUE);
    }
 
    variable_set('bah_custom_file_1_fid', '');
  }
}
BarisW’s picture

Title: managed_file form item in system settings form causes error » managed_file form item in system settings form causes error in form_state_values_clean()

Using a custom variable and unsetting the form_value works for me. Another way to get this working is by adding an is_array() check in forms.inc on line 2144:

Change:

<?php
    foreach ($parents as $parent) {
      $values = &$values[$parent];
    }
    unset($values[$last_parent]);
?>

To:

<?php
    foreach ($parents as $parent) {
      $values = &$values[$parent];
    }
    if (is_array($values)) {
      unset($values[$last_parent]);
    }
?>
BarisW’s picture

Version: 7.0 » 7.x-dev

By the way, this still applies in Drupal 7.x-dev

thursday_bw’s picture

+1 for BarisW's solution with the core patch.

I had the task of converting the user_profile form into an inline ajax save.

This was one of many challenges that needed solving in that case, when
converting the picture_uploaded field to ['#type'] => 'managed_field';
BarisW's solution would solve it conveniently.

My solution was to prepend a submit handler, and to unset all the buttons that had a submit handler
called 'file_managed_file_submit', (this avoided a core patch, and does the job for the custom module).
It is not ideal however as the default submit handler could be overridden for a such a field.

lsolesen’s picture

Status: Active » Needs review
StatusFileSize
new399 bytes

Rolled patch for the solution proposed by BarisW.

sun’s picture

Status: Needs review » Closed (duplicate)

Marking as duplicate of #635046: form_state_values_clean() is polluting form values (followup), which has a solution that was already accepted for D8, so just needs proper backporting/testing.

andriy’s picture

this worked perfectly for me, by unsetting upload and remove buttons added by managed_file

function custom_settings_form_submit($form, &$form_state) {
  global $user;
  
  // unset manged_file submit buttons
  foreach ($form_state['buttons'] as $ind => $button) {
    if (isset($button['#submit']) && $button['#submit'][0] == 'file_managed_file_submit') {
      unset($form_state['buttons'][$ind]);
    }
  }

  if (is_numeric($form_state['values']['custom_file_1']) && $form_state['values']['custom_file_1'] > 0) {
    // Load the file via file.fid.
    $file = file_load($form_state['values']['custom_file_1']);
    if ($file) {
      // Change status to permanent.
      $file->status = FILE_STATUS_PERMANENT;
      // Save.
      file_save($file);
      // Save file to variable.
      variable_set('custom_file_1_fid', $file->fid);
      // Record that the module (in this example, user module) is using the file.
      file_usage_add($file, 'user', 'user', $user->uid);
    }
  }
  else {
    // Load the file via file.fid.
    $file = file_load(variable_get('custom_file_1', ''));
   
    if ($file->fid) {
        // Delete the file and the usage record
        file_delete($file, TRUE);
    }

    variable_del('bah_custom_file_1_fid');
  }
}
andrezstar’s picture

<?php
#13 thanks for the explanation, you made my day.
Just a little addition:

On the deletion, shouldnt we check that the $file->fid exists? I mean, otherwise, when you have no file uploaded, it would be trying to delete it.

<?php

if ($fid >0){ //new condition

    $file = file_load(variable_get('custom_file_1', ''));
    if ($file->fid)
       {
           // Delete the file and the usage record
           file_delete($file, TRUE);
       }

    variable_del('bah_custom_file_1_fid');

}
?>
rahulkumar.it’s picture

thanks andriy for the explanation,You save my time.