Managed Files Within Drupal 7

By mrbagnall | 06 December 2014

Recently, I was tasked with creating a managed file space within a settings page on a custom Drupal module. This also occurs within the Drupal Administrative space. I soon discovered that Drupal's system_settings_form() function handles things a bit differently than other examples I have seen for dealing with managed fields.

The biggest is that system_settings_form() will manage the variables, but not deal at all with the files - including their default space. Using existing examples, the count associated with a managed file would increase everytime the settings page was saved because the system variable is set before the hook to update the file space. I have created an example module (below) which illustrates how to handle managed files within Drupal's administrative space so that you can safely and reliably set default files. In our case, it was default book and journal covers for specific kinds of content imported externally.

I have borrowed some of the theology from the examples module, but have had to modify it significantly to work within the context of system_settings_form().

I'll cite the pertinent parts of the module below and you can download the entire example at the bottom of this article. The first thing we need to do is define our managed field on our form.

/**
 * our example system settings form
 */
function mf_settings_form($form,&$form_state) {
  $form = array();

  $form['managed_file_example'] = array(
    '#type' => 'managed_file',
    '#title' => t('Managed File Example'),
    '#default_value' => variable_get('managed_file_example',''),
    '#upload_location' => 'public://managed_file_example/',
  );

  $form = system_settings_form($form);
  $form['#submit'][] = 'mf_settings_submit';

  return $form;
}

Notice we're also adding a submit handler mf_settings_submit() which will be used process our managed file settings. It's important to note that all of the system variables associated with the form are set before this callback is activated - so we need to plan accordingly.

The next step is to handle our scenarios. The following scenarios are possible with managed fields:

  1. Add a new file to an empty space
  2. Remove an existing file from a filled space
  3. Remove an existing file and replace it with a new file


Our module needs to be able to handle all three scenarios gracefully. Because the value of the system variable and the form_state variable will both be null if we remove a file, we need to look at the default value for the field in our form variable

/**
 * our processing form for managed fields. because system settings forms unset the
 * system variables before we get here, we need to check the default value from
 * the form and see if it matches what we are getting from the submitted values.
 */
 
function mf_settings_submit($form, $form_state) {
  /* get the original default files if one extsis */
  $managed_file_example = (!empty($form['managed_file_example']['#default_value'])) ? $form['managed_file_example']['#default_value'] : 0;

Note we need to get the existing file fid from the form definition and not the returned values. If there is a default value, it means something WAS there and we may need to deal with it depending on what we have to do next.

  /* if we have been provided files, make sure to mark them as in use */
  if (isset($form_state['values']['managed_file_example']) && $form_state['values']['managed_file_example']) {
    /**
     * if the uploaded file is different than the one we already have, then we need to
     * remove the current file and replace it with the new one
     */
    if (isset($managed_file_example) && $managed_file_example != $form_state['values']['managed_file_example']) {
      mf_remove_managed_file($managed_file_example, 'one');
      mf_add_managed_file($form_state['values']['managed_file_example'], 'example');
    }
    else {
      mf_add_managed_file($form_state['values']['managed_file_example'], 'example');
    }
  }
  elseif ($managed_file_one) {
    mf_remove_managed_file($managed_file_example, 'example');
  }
}

The comments do a pretty good job here, but basically we handle things as if we're uploading a new file, replacing a file (deleting the current one and adding a new one), or just deleting the existing file without replacing it. The functions we are calling in the code above are listed below and are also part of our sample module code.

function mf_remove_managed_file($managed_file, $which) {  
  // Retrieve the old file's id.
  $file = $managed_file ? file_load($managed_file) : FALSE;
  if ($file) {
    // When a module is managing a file, it must manage the usage count.
    // Here we decrement the usage count with file_usage_delete().
    file_usage_delete($file, 'mf', $which, $file->fid);

    // The file_delete() function takes a file object and checks to see if
    // the file is being used by any other modules. If it is the delete
    // operation is cancelled, otherwise the file is deleted.
    file_delete($file);
    drupal_set_message(t('The image @image_name was removed.', array('@image_name' => $file->filename)));
  }
}

function mf_add_managed_file($managed_file, $which) {
  /* if our file is already in use, then we don't need to re-do this and increase the count */
  $count = db_query('SELECT `count` FROM {file_usage} WHERE fid=:fid', array('fid' => $managed_file))->fetchField();
  if (empty($count)) {
    /* load the file via fid */
    $file = file_load($managed_file);
    /* change status to permanent */
    $file->status = FILE_STATUS_PERMANENT;
    /* save the file */
    file_save($file);
    /* record the file as in use */
    file_usage_add($file, 'mf', $which, $managed_file);
    unset($file);
  }
}

I've abstracted this code out just in case (as was in my case) you need to deal with multiple managed files in the administrative area. It prevents a lot of duplicate coding. I welcome your thoughts and suggestions in the comments section below or send your email comments to mbagnall@gmail.com.

The code itself (in a variant form) is available via the mTools module located at:

https://github.com/ElusiveMind/mtools