From c296c3befcd832d8340ab91608a0abb4e4f985e5 Mon Sep 17 00:00:00 2001
From: Gabor Szanto <hello@szantogabor.com>
Date: Sat, 19 Mar 2011 21:32:41 +0100
Subject: [PATCH] Issue #578442: Rerolled patch #37 and remove $module_name() from filefield_paths_features_pipe_content_alter()

---
 filefield_paths.features.inc |  186 +++++
 filefield_paths.module       | 1674 +++++++++++++++++++++---------------------
 2 files changed, 1035 insertions(+), 825 deletions(-)
 create mode 100644 filefield_paths.features.inc

diff --git a/filefield_paths.features.inc b/filefield_paths.features.inc
new file mode 100644
index 0000000..3928262
--- /dev/null
+++ b/filefield_paths.features.inc
@@ -0,0 +1,186 @@
+<?php
+/**
+ * @file additional routines to add 'Features' exportable support to this
+ * module.
+ * 
+ * Per-field filefield_path preferences can be selected to be packaged within a
+ * feature bundle.
+ * 
+ * This will happen automatically when using features to export a content type,
+ * or an individual field, or can be chosen to be packaged on its own.
+ * 
+ * @author dman dan@coders.co.nz 2010-11
+ * 
+ * (An experiment with getting features more widely supported)
+ */
+
+/**
+ * Implementation of hook_features_export_options().
+ * 
+ * List all filefield_paths config settings currently available for export. This
+ * adds each of the configurations to the features UI where they can be chosen
+ * for bundling.
+ * 
+ * This UI may be entirely unneccessary if we only ever export as a part of
+ * something else (individual fields settings), but it's here for completeness.
+ * 
+ * @return array A keyed array of items, suitable for use with a FormAPI select
+ * or checkboxes element.
+ * 
+ **/
+function filefield_paths_item_features_export_options() {
+  $options = array();
+  $sql = db_rewrite_sql("SELECT * FROM {filefield_paths}");
+  $result = db_query($sql);
+  while ($row = db_fetch_array($result)) {
+    // Generate the unique keys that can identify the row 
+    // "{node_type}-{field}" eg "story-field_illustration"
+    $key = "{$row['type']}-{$row['field']}";
+    $options[$key] = $key;
+  }
+  return $options;
+}
+
+
+/**
+ * Implementation of hook_features_export().
+ * 
+ * Process the export array for a given component.
+ * 
+ * Normally, we will be adding this as a child in the pipe of
+ * content_features_export, so that when a filefield instance is exported, this
+ * setting was published along with it.
+ */
+function filefield_paths_item_features_export($data, &$export, $module_name = '') {
+  $export['dependencies']['filefield_paths'] = 'filefield_paths';
+  foreach ($data as $identifier) {
+    if ($wrapper = filefield_paths_item_load($identifier)) {
+      $export['features']['filefield_paths_item'][$identifier] = $identifier;
+    }
+  }
+  #$pipe = array();
+  #return $pipe;
+}
+
+/**
+ * Attach our own export routine as a piped export that happens below any cck
+ * filefield path that is getting exported. 
+ * 
+ * The component name for cck fields is 'content'
+ * 
+ * HOOK_features_pipe_COMPONENT_alter()
+ * 
+ * This captures each cck field export, and adds ourself to the dependencies and
+ * exports when that field is exported.
+ */
+function filefield_paths_features_pipe_content_alter(&$pipe, $data, $export) {
+  foreach ($data as $field_identifier) {
+    
+    // CCK field export is exporting a field named $field_identifier.
+    // If that is a filefield, we should attach ourselves as a subprocess (pipe).
+    // .. actually, don't need to check the field type, 
+    // just see if we have some filefield_path
+    // settings that use the same $field_identifier key! 
+    
+    if (filefield_paths_item_load($field_identifier)) {
+      // So add this setting as a piped child of the filed when it gets exported.
+      $pipe['filefield_paths_item'][$field_identifier] = $field_identifier;
+    }
+  }
+}
+      
+
+/**
+ * Return the required path settings for the named filefield instance.
+ * 
+ * A CRUD utility for filefield_paths
+ * 
+ * @param a unique identifier for the given field instance - {$type-$field}
+ * This identifier pattern is the same that features.content.inc uses
+ * 
+ * @return a row array from the filefield_paths DB table - with the 'serialized'
+ * blobs unpacked.
+ */
+function filefield_paths_item_load($identifier) {
+  list($type_name, $field_name) = explode('-', $identifier); 
+  $row = db_fetch_array(
+    db_query("SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $type_name, $field_name)
+  );
+  if (!empty($row)) {
+    $ffp = array();
+    // Each cell in the row gets exposed, retrieve the schema to figure this out.
+    $schema = drupal_get_schema('filefield_paths');
+    foreach ($schema['fields'] as $field => $field_def) {
+      $ffp[$field] = empty($field_def['serialize']) ? $row[$field] : unserialize($row[$field]);
+    }
+    
+    return $ffp;
+  }
+  return NULL;
+}
+
+/**
+ * Delete the identified row
+ * 
+ * A CRUD utility for filefield_paths
+ */
+function filefield_paths_item_delete($identifier) {
+  list($type_name, $field_name) = explode('-', $identifier); 
+  db_query("DELETE FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $type_name, $field_name);
+}
+
+
+/**
+ * Implementation of hook_features_export_render()
+ * 
+ * Return the PHP code that represents a dump of the settings listed as $data
+ */
+function filefield_paths_item_features_export_render($module, $data) {
+  $code = array();
+  $code[] = '  $settings = array();';
+  $code[] = '';
+
+  $translatables = array();
+  foreach ($data as $item_id) {
+    $item = filefield_paths_item_load($item_id);
+    if (empty($item)) {
+      watchdog('filefield_paths', "When trying to prepare the export code, failed to retrieve the filefield path settings '%item_id'. Odd.", array('%item_id' => $item_id), WATCHDOG_WARNING);
+      continue;
+    }
+    $code[] = "  // Exported {$item_id}";
+    $export = features_var_export($item, '  ');
+    $code[] = "  \$settings['{$item_id}'] = {$export};";
+  }
+
+  $code[] = '';
+  $code[] = '  return $settings;';
+  $code = implode("\n", $code);
+  return array('filefield_paths_item_default_items' => $code);
+}
+
+/**
+ * Implementation of hook_features_export_revert().
+ */
+function filefield_paths_item_features_revert($module) {
+  filefield_paths_item_features_rebuild($module);
+}
+
+/**
+ * Create/recreate the items based on the data array. Data should contain a
+ * number of filefield_paths_item definitions. 
+ * 
+ * Implementation of hook_features_export_rebuild().
+ * 
+ * Data just need to be put straight into the database as rows.
+ */
+function filefield_paths_item_features_rebuild($module) {
+  if ($defaults = features_get_default('filefield_paths_item', $module)) {
+    foreach ($defaults as $filefield_paths_id => $filefield_paths_item) {
+      // Delete any previous settings for this item.
+      if (filefield_paths_item_load($filefield_paths_id)) {
+        filefield_paths_item_delete($filefield_paths_id);
+      }
+      drupal_write_record('filefield_paths', $filefield_paths_item);
+    }
+  }
+}
diff --git a/filefield_paths.module b/filefield_paths.module
index e0e260f..7885f56 100644
--- a/filefield_paths.module
+++ b/filefield_paths.module
@@ -1,835 +1,859 @@
-<?php
-/**
- * @file
- * Contains core functions for the FileField Paths module.
- */
-
-/**
- * Implements hook_filefield_paths_field_settings().
- */
-function filefield_paths_filefield_paths_field_settings() {
-  return array(
-    'file_path' => array(
-      'title' => 'File path',
-      'sql' => 'filepath',
-
-      'form' => array(
-        'file_path' => array(
-          '#maxlength' => 512,
-          '#size' => 128,
-        ),
-      ),
-    ),
-
-    'file_name' => array(
-      'title' => 'File name',
-      'sql' => 'filename',
-
-      'form' => array(
-        'file_name' => array(
-          '#type' => 'textfield',
-          '#title' => t('File name'),
-          '#default_value' => '[filefield-onlyname-original].[filefield-extension-original]',
-        ),
-      ),
-    )
-  );
-}
-
-/**
+<?php
+/**
+ * @file
+ * Contains core functions for the FileField Paths module.
+ */
+
+/**
+ * Implements hook_filefield_paths_field_settings().
+ */
+function filefield_paths_filefield_paths_field_settings() {
+  return array(
+    'file_path' => array(
+      'title' => 'File path',
+      'sql' => 'filepath',
+
+      'form' => array(
+        'file_path' => array(
+          '#maxlength' => 512,
+          '#size' => 128,
+        ),
+      ),
+    ),
+
+    'file_name' => array(
+      'title' => 'File name',
+      'sql' => 'filename',
+
+      'form' => array(
+        'file_name' => array(
+          '#type' => 'textfield',
+          '#title' => t('File name'),
+          '#default_value' => '[filefield-onlyname-original].[filefield-extension-original]',
+        ),
+      ),
+    )
+  );
+}
+
+/**
  * Implements hook_theme().
  */
-function filefield_paths_theme() {
-  return array(
-    'filefield_paths_token_help' => array(
-      'arguments' => array('prefix' => '[', 'suffix' => ']')
-    ),
-  );
-}
-
-function theme_filefield_paths_token_help($prefix = '[', $suffix = ']') {
-  token_include();
-  $full_list = array_merge(filefield_paths_token_list(), token_get_list('node'));
-  if (function_exists('filefield_token_list')) {
-    $temp_tokens = filefield_token_list();
-    $full_list['file'] = array_merge($temp_tokens['file'], $full_list['file']);
-  }
-
-  $headers = array(t('Token'), t('Replacement value'));
-  $rows = array();
-  foreach ($full_list as $key => $category) {
-    $rows[] = array(array('data' => drupal_ucfirst($key) . ' ' . t('tokens'), 'class' => 'region', 'colspan' => 2));
-    foreach ($category as $token => $description) {
-      $row = array();
-      $row[] = $prefix . $token . $suffix;
-      $row[] = $description;
-      $rows[] = $row;
-    }
-  }
-
-  return theme('table', $headers, $rows, array('class' => 'description'));
-}
-
-/**
- * Implements hook_init().
+function filefield_paths_theme() {
+  return array(
+    'filefield_paths_token_help' => array(
+      'arguments' => array('prefix' => '[', 'suffix' => ']')
+    ),
+  );
+}
+
+function theme_filefield_paths_token_help($prefix = '[', $suffix = ']') {
+  token_include();
+  $full_list = array_merge(filefield_paths_token_list(), token_get_list('node'));
+  if (function_exists('filefield_token_list')) {
+    $temp_tokens = filefield_token_list();
+    $full_list['file'] = array_merge($temp_tokens['file'], $full_list['file']);
+  }
+
+  $headers = array(t('Token'), t('Replacement value'));
+  $rows = array();
+  foreach ($full_list as $key => $category) {
+    $rows[] = array(array('data' => drupal_ucfirst($key) . ' ' . t('tokens'), 'class' => 'region', 'colspan' => 2));
+    foreach ($category as $token => $description) {
+      $row = array();
+      $row[] = $prefix . $token . $suffix;
+      $row[] = $description;
+      $rows[] = $row;
+    }
+  }
+
+  return theme('table', $headers, $rows, array('class' => 'description'));
+}
+
+/**
+ * Implements hook_init().
  */
 function filefield_paths_init() {
-  foreach (module_list() as $module) {
-    if (file_exists($file = drupal_get_path('module', 'filefield_paths') . '/modules/' . $module . '.inc')) {
-      require_once $file;
-    }
-  }
-}
-
+  foreach (module_list() as $module) {
+    if (file_exists($file = drupal_get_path('module', 'filefield_paths') . '/modules/' . $module . '.inc')) {
+      require_once $file;
+    }
+  }
+}
+
 /**
- * Implements hook_form_alter().
- */
-function filefield_paths_form_alter(&$form, $form_state, $form_id) {
-  $ffp = array();
-
-  // Invoke hook_filefield_paths_form_alter().
-  foreach (module_implements('filefield_paths_form_alter') as $module) {
-    $function = $module . '_filefield_paths_form_alter';
-    $function($form, $ffp);
-  }
-
-  // If supporting module enabled, show FileField Paths settings form.
-  if (count($ffp) > 0) {
-    $fields = module_invoke_all('filefield_paths_field_settings');
-    foreach ($ffp as $field_name => $field_data) {
-
-      $result = db_fetch_object(
-        db_query("SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $field_data['type'], $field_name)
-      );
-      if (!empty($result)) {
-        foreach ($fields as &$field) {
-          $field['settings'] = unserialize($result->$field['sql']);
-        }
-        unset($field);
-      }
-
-      $count = 0;
-      foreach ($fields as $name => $field) {
-        $count++;
-
-        if (isset($field['form']) && is_array($field['form'])) {
-          $keys = array_keys($field['form']);
-          for ($i = 1; $i < count($field['form']); $i++) {
-            $field['form'][$keys[$i]]['#weight'] = ($count - 1) * 3 + 2 + $i;
-          }
-          unset($keys);
-
-          $field_data['form_path'] = array_merge_recursive($field_data['form_path'], $field['form']);
-        }
-
-        $field_data['form_path']['#tree'] = TRUE;
-        $field_data['form_path'][$name]['#weight'] = ($count - 1) * 3;
-
-        // Set defualt value for patterns.
-        if (isset($field['settings']['value'])) {
-          $field_data['form_path'][$name]['#default_value'] = $field['settings']['value'];
-
-          if (isset($field['data'])) {
-            foreach ($field['data'] as $key => $value) {
-              $field_data['form_path'][$value]['#default_value'] = $field['settings'][$key];
-            }
-          }
-        }
-
+ * Implements hook_form_alter().
+ */
+function filefield_paths_form_alter(&$form, $form_state, $form_id) {
+  $ffp = array();
+
+  // Invoke hook_filefield_paths_form_alter().
+  foreach (module_implements('filefield_paths_form_alter') as $module) {
+    $function = $module . '_filefield_paths_form_alter';
+    $function($form, $ffp);
+  }
+
+  // If supporting module enabled, show FileField Paths settings form.
+  if (count($ffp) > 0) {
+    $fields = module_invoke_all('filefield_paths_field_settings');
+    foreach ($ffp as $field_name => $field_data) {
+
+      $result = db_fetch_object(
+        db_query("SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $field_data['type'], $field_name)
+      );
+      if (!empty($result)) {
+        foreach ($fields as &$field) {
+          $field['settings'] = unserialize($result->$field['sql']);
+        }
+        unset($field);
+      }
+
+      $count = 0;
+      foreach ($fields as $name => $field) {
+        $count++;
+
+        if (isset($field['form']) && is_array($field['form'])) {
+          $keys = array_keys($field['form']);
+          for ($i = 1; $i < count($field['form']); $i++) {
+            $field['form'][$keys[$i]]['#weight'] = ($count - 1) * 3 + 2 + $i;
+          }
+          unset($keys);
+
+          $field_data['form_path'] = array_merge_recursive($field_data['form_path'], $field['form']);
+        }
+
+        $field_data['form_path']['#tree'] = TRUE;
+        $field_data['form_path'][$name]['#weight'] = ($count - 1) * 3;
+
+        // Set defualt value for patterns.
+        if (isset($field['settings']['value'])) {
+          $field_data['form_path'][$name]['#default_value'] = $field['settings']['value'];
+
+          if (isset($field['data'])) {
+            foreach ($field['data'] as $key => $value) {
+              $field_data['form_path'][$value]['#default_value'] = $field['settings'][$key];
+            }
+          }
+        }
+
         $field_data['form_path'][$name . '_cleanup'] = array(
-          '#type' => 'fieldset',
-          '#title' => t('!title cleanup settings', array('!title' => $field['title'])),
-          '#collapsible' => TRUE,
-          '#collapsed' => TRUE,
-          '#weight' => ($count - 1) * 3 + 1,
-          '#attributes' => array(
+          '#type' => 'fieldset',
+          '#title' => t('!title cleanup settings', array('!title' => $field['title'])),
+          '#collapsible' => TRUE,
+          '#collapsed' => TRUE,
+          '#weight' => ($count - 1) * 3 + 1,
+          '#attributes' => array(
             'class' => array($name . ' cleanup')
-          )
-        );
-
-        // Cleanup field with Pathauto module.
+          )
+        );
+
+        // Cleanup field with Pathauto module.
         $field_data['form_path'][$name . '_cleanup'][$name . '_pathauto'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Cleanup using Pathauto') . '.',
-          '#default_value' => isset($field['settings']['pathauto'])
-            ? $field['settings']['pathauto']
-            : 0
-          ,
-          '#description' => t('Cleanup !title using !url', array('!title' => $field['title'], '!url' => l(t('Pathauto settings'), 'admin/build/path/pathauto'))),
-        );
-        if (!module_exists('pathauto')) {
+          '#type' => 'checkbox',
+          '#title' => t('Cleanup using Pathauto') . '.',
+          '#default_value' => isset($field['settings']['pathauto'])
+            ? $field['settings']['pathauto']
+            : 0
+          ,
+          '#description' => t('Cleanup !title using !url', array('!title' => $field['title'], '!url' => l(t('Pathauto settings'), 'admin/build/path/pathauto'))),
+        );
+        if (!module_exists('pathauto')) {
           $field_data['form_path'][$name . '_cleanup'][$name . '_pathauto']['#disabled'] = TRUE;
           $field_data['form_path'][$name . '_cleanup'][$name . '_pathauto']['#default_value'] = 0;
-        }
-
-        // Convert field to lower case.
-        $field_data['form_path'][$name . '_cleanup'][$name . '_tolower'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Convert to lower case') . '.',
-          '#default_value' => isset($field['settings']['tolower'])
-            ? $field['settings']['tolower']
-            : 0
-          ,
-          '#description' => t('Convert !title to lower case', array('!title' => $field['title'])) . '.'
-        );
-
-        // Transliterate field with Transliteration module.
-        $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Transliterate') . '.',
-          '#default_value' => isset($field['settings']['transliterate'])
-            ? $field['settings']['transliterate']
-            : 0
-          ,
-          '#description' => t('Transliterate !title', array('!title' => $field['title'])) . '.'
-        );
-        if (!module_exists('transliteration')) {
-          $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate']['#disabled'] = TRUE;
-          $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate']['#default_value'] = 0;
-        }
-
-        // Replacement patterns for field.
-        $field_data['form_path'][$name . '_tokens'] = array(
-          '#type' => 'fieldset',
-          '#title' => t('!title replacement patterns', array('!title' => $field['title'])),
-          '#collapsible' => TRUE,
-          '#collapsed' => TRUE,
-          '#description' => theme('filefield_paths_token_help'),
-          '#weight' => ($count - 1) * 3 + 2,
-          '#attributes' => array(
-            'class' => $name . ' tokens'
-          )
-        );
-      }
-
-      // Retroactive updates.
-      $field_data['form_path']['retroactive_update'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('Retroactive update'),
-        '#description' => t('Move and rename previously uploaded files') . '.' .
-          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
-          t('This feature should only be used on developmental servers or with extreme caution') . '.',
-        '#weight' => 10
-      );
-
-      // Active updating.
-      $field_data['form_path']['active_updating'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('Active updating'),
-        '#default_value' => variable_get("ffp_{$field_data['type']}_{$field_name}", 0),
-        '#description' => t('Actively move and rename previously uploaded files as required') . '.' .
-          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
-          t('This feature should only be used on developmental servers or with extreme caution') . '.',
-        '#weight' => 11
-      );
-
-      if (!in_array('filefield_paths_form_submit', $form['#submit'])) {
-        $form['#submit'][] = 'filefield_paths_form_submit';
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_form_submit().
- */
-function filefield_paths_form_submit($form, &$form_state) {
-  $ffp = array();
-
-  // Invoke hook_filefield_paths_form_submit().
-  foreach (module_implements('filefield_paths_form_submit') as $module) {
-    $function = $module . '_filefield_paths_form_submit';
-    $function($form_state, $ffp);
-  }
-
-  if (count($ffp) > 0) {
-    $retroactive_update = FALSE;
-    $fields = module_invoke_all('filefield_paths_field_settings');
-    foreach ($ffp as $field_name => $field_data) {
-      $cols = $vals = $data = array();
-
-      foreach ($fields as $name => &$field) {
-        $field['settings'] = array(
-          'value' => $form_state['values']['ffp_' . $field_name][$name],
-          'tolower' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_tolower'],
-          'pathauto' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_pathauto'],
-          'transliterate' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_transliterate']
-        );
-
-        // Store additional settings from addon modules.
-        if (isset($field['data'])) {
-          foreach ($field['data'] as $key => $value) {
-            $field['settings'][$key] = $form_state['values']['ffp_' . $field_name][$value];
-          }
-        }
-
-        $cols[] = $field['sql'];
-        $vals[] = "'%s'";
-        $data[] = serialize($field['settings']);
-      }
-
-      $result = db_fetch_object(
-        db_query(
-          "SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'",
-          $field_data['type'], $field_name
-        )
-      );
-
-      // Update existing entry.
-      if (!empty($result)) {
-        foreach ($cols as &$col) {
-          $col .= " = '%s'";
-        }
-
-        db_query(
-          "UPDATE {filefield_paths} SET " . implode(', ', $cols) . " WHERE type = '%s' AND field = '%s'",
-          array_merge($data, array($field_data['type'], $field_name))
-        );
-      }
-
-      // Create new entry.
-      else {
-        db_query(
-          "INSERT INTO {filefield_paths} (type, field, " . implode(', ', $cols) . ") VALUES ('%s', '%s', " . implode(', ', $vals) . ")",
-          array_merge(array($field_data['type'], $field_name), $data)
-        );
-      }
-
-      if ($form_state['values']['ffp_' . $field_name]['retroactive_update']) {
-        $retroactive_update = TRUE;
-        $module = isset($form['#field']) ? $form['#field']['module'] : $field_name;
-        filefield_paths_batch_update($module, $field_name, arg(3));
-      }
-
-      variable_set("ffp_{$field_data['type']}_{$field_name}", $form_state['values']['ffp_' . $field_name]['active_updating']);
-    }
-
-    if ($retroactive_update) {
-      // Run batch.
-      batch_process($form_state['redirect']);
-    }
-  }
-}
-
-/**
- * Set batch process to update FileField Paths.
- *
- * @param $module
- *   The upload widgets module ('filefield', 'upload', etc).
- * @param $field
- *   The CCK field name - set as NULL if not applicable.
- * @param $type
- *   The Node type ('page', 'story', etc).
- */
-function filefield_paths_batch_update($module, $field, $type) {
-  $objects = array();
-
-  // Invoke hook_filefield_paths_batch_update().
-  if (function_exists($function = $module . '_filefield_paths_batch_update')) {
-    $function($field, str_replace('-', '_', $type), $objects);
-  }
-
-  // Create batch.
-  $batch = array(
-    'title' => t('Updating FileField Paths'),
-    'operations' => array(
-      array('_filefield_paths_batch_update_process', array(array_unique($objects), $module, $field))
-    ),
-  );
-  batch_set($batch);
-}
-
-function _filefield_paths_batch_update_process($objects, $module, $field, &$context) {
-  if (!isset($context['sandbox']['progress'])) {
-    $context['sandbox']['progress'] = 0;
-    $context['sandbox']['max'] = count($objects);
-    $context['sandbox']['objects'] = $objects;
-  }
-
-  // Process nodes by groups of 5.
-  $count = min(5, count($context['sandbox']['objects']));
-  for ($i = 1; $i <= $count; $i++) {
-    // For each oid, load the object, update the files and save it.
-    $oid = array_shift($context['sandbox']['objects']);
-
-    // Invoke hook_filefield_paths_update().
-    if (function_exists($function = $module . '_filefield_paths_update')) {
-      $function($oid, $field);
-    }
-
-    // Update our progress information.
-    $context['sandbox']['progress']++;
-  }
-
-  // Inform the batch engine that we are not finished,
-  // and provide an estimation of the completion level we reached.
-  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
-    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
-  }
-}
-
-/**
- * Implements hook_nodeapi().
- */
-function filefield_paths_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
-  $function = 'filefield_paths_node_'. $op;
-  if (function_exists($function)) {
-    $function($node);
-  }
-}
-
-/**
- * Implements hook_node_presave().
- */
-function filefield_paths_node_presave(&$node) {
-  if (module_exists('content')) {
-    $content_type = content_types($node->type);
-
-    foreach ($content_type['fields'] as $field) {
-      if ($field['type'] == 'filefield' && is_array($node->$field['field_name'])) {
-
-        foreach ($node->$field['field_name'] as $count => &$file) {
-          if (!is_array($file) || empty($file['filepath'])) {
-            continue;
-          }
-
-          // If file is newly uploaded, flag to be processed
-          if ($file['status'] == 0) {
-            $file['data']['process'] = TRUE;
-          }
-        }
-
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_node_insert().
- */
-function filefield_paths_node_insert(&$node) {
-  filefield_paths_node_update($node);
-}
-
-/**
- * Implements hook_node_update().
- */
-function filefield_paths_node_update(&$node) {
-  if (($ffp = filefield_paths_get_fields($node)) !== FALSE) {
-    $update = new stdClass;
-    $update->node = FALSE;
-
-    // Process files.
-    foreach ($ffp['#files'] as &$file) {
-      // Invoke hook_filefield_paths_process_file().
-      foreach (module_implements('filefield_paths_process_file') as $module) {
-        $function = $module . '_filefield_paths_process_file';
-        $function(($file['new'] || variable_get("ffp_{$node->type}_{$file['name']}", 0)), $file, $ffp['#settings'][$file['name']], $node, $update);
-      }
-    }
-
-    // Re-write node entry if required.
-    if ($update->node == TRUE) {
-      global $user;
-
-      drupal_write_record('node', $node, 'nid');
-      _node_save_revision($node, $user->uid, 'vid');
-    }
-
-    // Re-write cck fields.
-    if (module_exists('content')) {
-      if (module_exists('filefield')) {
-        _field_file_cache(NULL, TRUE);
-      }
-      _content_field_invoke_default('update', $node);
-      cache_clear_all('content:'. $node->nid . ':' . $node->vid, content_cache_tablename());
-    }
-
-    // Cleanup temporary paths.
-    if ($ffp['#settings']) {
-      foreach ($ffp['#settings'] as $name => $field) {
-        $paths = explode('/', $field['filepath']['value']);
-        filefield_paths_cleanup_temp($paths);
-
-        // Invoke hook_filefield_paths_cleanup().
-        foreach (module_implements('filefield_paths_cleanup') as $module) {
-          $function = $module . '_filefield_paths_cleanup';
-          $function($ffp, $paths, $name);
-        }
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_content_fieldapi().
- */
-function filefield_paths_content_fieldapi($op, $field) {
-  switch ($op) {
-    case 'delete instance':
-      db_query(
-        "DELETE FROM {filefield_paths} WHERE type = '%s' AND field = '%s'",
-        $field['type_name'], $field['field_name']
-      );
-      break;
-  }
-}
-
-function filefield_paths_cleanup_temp($paths) {
-  while ($paths) {
-    if (@rmdir(file_directory_path() . '/' . implode('/', $paths)) === TRUE) {
-      array_pop($paths);
-      continue;
-    }
-    break;
-  }
-}
-
-function filefield_paths_get_fields(&$node, $op = NULL) {
-  $ffp = array();
-
-  // Invoke hook_filefield_paths_get_fields().
-  foreach (module_implements('filefield_paths_get_fields') as $module) {
-    $function = $module . '_filefield_paths_get_fields';
-    $function($node, $ffp);
-  }
-
-  if (count($ffp) == 0 || (isset($ffp['#types']) && !is_array($ffp['#types']))) {
-    return FALSE;
-  }
-
-  $fields = module_invoke_all('filefield_paths_field_settings');
-
-  // Load fields settings
-  foreach ($ffp['#types'] as $name => $temp) {
-    $result = db_fetch_object(
-      db_query("SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $node->type, $name)
-    );
-
-    if (!empty($result)) {
-      foreach ($fields as $field) {
-        $ffp['#settings'][$name][$field['sql']] = unserialize($result->$field['sql']);
-      }
-    }
-  }
-
-  return $ffp;
-}
-
-/**
- * Implements hook_filefield_paths_process_file().
- */
-function filefield_paths_filefield_paths_process_file($new, &$file, $settings, &$node, &$update) {
-  if ($new) {
-    // Process filename
-    $file['filename']['old'] = $file['field']['filename'];
-    if (($file['filename']['new'] = $settings['filename']['value']) != '') {
-      $file['filename']['new'] = filefield_paths_process_string($file['filename']['new'], 'node', $node, $settings['filename']);
-      $file['filename']['new'] = filefield_paths_process_string($file['filename']['new'], 'field', array(0 => $file['field']), $settings['filename']);
-    }
-    else {
-      $file['filename']['new'] = $file['field']['filename'];
-    }
-  
-    // Process filepath
-    $file['filepath']['old'] = $file['field']['filepath'];
-    $file['filepath']['new'] = filefield_paths_process_string(file_directory_path() . '/' . $settings['filepath']['value'] . '/' . $file['filename']['new'], 'node', $node, $settings['filepath']);
-    $file['filepath']['new'] = filefield_paths_process_string($file['filepath']['new'], 'field', array(0 => $file['field']), $settings['filepath']);
-
-    // Finalize files if necessary
-    if (dirname($file['filepath']['new']) != dirname($file['field']['filepath']) || $file['filename']['new'] != $file['field']['filename']) {
-      if (filefield_paths_file_move($file)) {
-
-        // Fix reference to old paths.
-        if (isset($node->body) || module_exists('content')) {
-          $file['filepath']['new'] = str_replace($file['filename']['old'], $file['filename']['new'], $file['filepath']['new']);
-          $file_directory_path = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC ? file_directory_path() : 'system/files';
-
-          // Regular expression to replace old file reference.
-          $pattern = array(
-            'regex' => str_replace('/', '\/', $file_directory_path) . '([^"]*?)' . str_replace('/', '\/', str_replace(file_directory_path(), '', $file['filepath']['old'])),
-            'regex_enc' => str_replace('/', '\/', drupal_urlencode($file_directory_path)) . '([^"]*?)' . str_replace('/', '\/', str_replace(drupal_urlencode(file_directory_path()), '', drupal_urlencode($file['filepath']['old']))),
-            'replace' => $file_directory_path . '$1' . str_replace(file_directory_path(), '', $file['filepath']['new']),
-          );
-
-          // Process regular expression.
-          _filefield_paths_replace_pattern($pattern, $node, $update);
-        }
-
-        // Store new filename in file Array
-        $file['field']['filename'] = $file['filename']['new'];
-      }
-    }
-  }
-}
-
-/**
- * Run regular expression over all available text-based fields.
- */
-function _filefield_paths_replace_pattern($pattern, &$node, &$update) {
-  $fields = _filefield_paths_text_fields($node);
-  // Loop through all fields and process filter.
-  foreach ($fields as $field => &$data) {
-    ${$field} = (preg_match('/' . $pattern['regex'] . '/s', $data))
-      ? preg_replace('/' . $pattern['regex'] . '/s', $pattern['replace'], $data)
-      : preg_replace('/' . $pattern['regex_enc'] . '/s', $pattern['replace'], $data);
-
-    if (${$field} != $data) {
-      $data = ${$field};
-      // Mark node for update.
-      if ($field == 'body' || $field == 'teaser') {
-        $update->node = TRUE;
-      }
-    }
-  }
-}
-
-/**
- * Get all text-based fields attached to node.
- */
-function _filefield_paths_text_fields($node) {
-  $fields = array();
-  // Get Node fields.
-  if (isset($node->body)) {
-    $fields += array(
-      'body' => &$node->body,
-      'teaser' => &$node->teaser,
-    );
-  }
-
-  // Get CCK fields.
-  if (module_exists('content')) {
-    $content_type = content_types($node->type);
-    foreach ($content_type['fields'] as $name => $field) {
-      if ($field['type'] == 'text' && is_array($node->{$name})) {
-        foreach ($node->{$name} as $key => $value) {
-          if (isset($node->{$name}[$key]['value'])) {
-            $fields["{$name}_{$key}"] = &$node->{$name}[$key]['value'];
-          }
-        }
-      }
-    }
-  }
-
-  return $fields;
-}
-
-/**
- * Implements hook_token_list().
- */
-function filefield_paths_token_list($type = 'all') {
-  if ($type == 'field' || $type == 'all') {
-    $tokens = array();
-    if (!module_exists('filefield')) {
-      $tokens['file']['filefield-onlyname'] = t("File name without extension");
-      $tokens['file']['filefield-extension'] = t("File extension");
-    }
-    $tokens['file']['filefield-onlyname-original'] = t("File name without extension - original");
-    $tokens['file']['filefield-extension-original'] = t("File extension - original");
-    // Tokens for Image module integration.
-    if (module_exists('image')) {
-      $tokens['image']['image-derivative'] = t("Image derivative (thumbnail, preview, etc)");
-    }
-    return $tokens;
-  }
-}
-
-/**
- * Implements hook_token_values().
- */
-function filefield_paths_token_values($type, $object = NULL) {
-  if ($type == 'field') {
-    $item = pathinfo($object[0]['filename']);
-    $tokens = array();
-    if (!module_exists('filefield')) {
-      // PHP < 5.2: pathinfo() doesn't return 'filename' variable.
-      $tokens['filefield-onlyname'] = isset($item['filename'])
-        ? $item['filename']
-        : basename($object[0]['filename'], '.' . $item['extension']);
-      $tokens['filefield-extension'] = $item['extension'];
-    }
-    // Original filename.
-    $orig = $item;
-    $filename = $object[0]['filename'];
-    $result = db_fetch_object(db_query("SELECT origname FROM {files} WHERE fid = %d", $object[0]['fid']));
-    if (!empty($result->origname)) {
-      $object[0]['origname'] = $result->origname;
-    }
-    if (!empty($object[0]['origname'])) {
-      $orig = pathinfo($object[0]['origname']);
-      $filename = $object[0]['origname'];
-    }
-    // PHP < 5.2: pathinfo() doesn't return 'filename' variable.
-    $tokens['filefield-onlyname-original'] = isset($orig['filename'])
-      ? $orig['filename']
-      : basename($filename, '.' . $orig['extension']);
-    $tokens['filefield-extension-original'] = $orig['extension'];
-    // Tokens for Image module integration.
-    if (module_exists('image')) {
-      $tokens['image-derivative'] = (isset($object[0]['type']) && $object[0]['type'] !== '_original')
-        ? '.' . $object[0]['type']
-        : '';
-    }
-    return $tokens;
-  }
-}
-
-/**
- * Process and cleanup strings.
- */
-function filefield_paths_process_string($value, $type, $object, $settings = array()) {
-
-  // Process string tokens.
-  $placeholders = _filefield_paths_get_values($type, $object, (module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto']));
-  $value = str_replace($placeholders['tokens'], $placeholders['values'], $value);
-
-  // Transliterate string.
-  if (module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate']) {
-    $value = transliteration_get($value);
-    if ($type == 'field') {
-      $paths = explode('/', $value);
-      foreach ($paths as &$path) {
-        $path = transliteration_clean_filename($path);
-      }
-
-      $value = implode('/', $paths);
-    }
-  }
-
-  // Convert string to lower case.
-  if ((isset($settings['tolower']) && $settings['tolower']) || (isset($settings['pathauto']) && $settings['pathauto'] && variable_get('pathauto_case', 0))) {
-    // Convert string to lower case
-    $value = drupal_strtolower($value);
-  }
-
-  // Ensure that there are no double-slash sequences due to empty token values.
-  $value = preg_replace('/\/+/', '/', $value);
-
-  return $value;
-}
-
-function _filefield_paths_get_values($type, $object, $pathauto = FALSE) {
-  switch ($type) {
-    case 'node':
-      $full = token_get_values($type, $object, TRUE);
-      break;
-
-    case 'field':
-      $all = filefield_paths_token_values($type, $object);
-      if (function_exists('filefield_token_values')) {
-        $all = array_merge(filefield_token_values($type, $object), $all);
-      }
-
-      $full = new stdClass();
-      $full->tokens = array_keys($all);
-      $full->values = array_values($all);
-      break;
-  }
-
-  $full->tokens = token_prepare_tokens($full->tokens);
-  if ($pathauto) {
-    $temp = clone $full;
-
-    // Strip square brackets from tokens for Pathauto.
-    foreach ($temp->tokens as &$token) {
-      $token = trim($token, "[]");
-    }
-
-    if (!function_exists('pathauto_clean_token_values')) {
-      module_load_include('inc', 'pathauto');
-    }
-    $full->values = pathauto_clean_token_values($temp);
-  }
-
-  return array('tokens' => $full->tokens, 'values' => $full->values);
-}
-
-/**
- * Move file and update its database record.
- */
-function filefield_paths_file_move(&$file, $replace = FILE_EXISTS_RENAME) {
-  $dest = _filefield_paths_strip_path(dirname($file['filepath']['new']));
-
-  foreach (explode('/', $dest) as $dir) {
-    $dirs[] = $dir;
-    $path = file_create_path(implode($dirs, '/'));
-    if (!_filefield_paths_check_directory($path, FILE_CREATE_DIRECTORY)) {
-      watchdog('filefield_paths', 'FileField Paths failed to create directory (%d).', array('%d' => $path), WATCHDOG_ERROR);
-      return FALSE;
-    }
-  }
-
-  if (!file_move($file['field']['filepath'], $dest . '/' . $file['filename']['new'], $replace)) {
-    watchdog('filefield_paths', 'FileField Paths failed to move file (%o) to (%n).', array('%o' => $file['filepath']['old'], '%n' => $dest .'/'. $file['filename']['new']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-
-  $result = db_fetch_object(db_query("SELECT origname FROM {files} WHERE fid = %d", $file['field']['fid']));
-
-  // Set 'origname' and update 'filename'.
-  if (empty($result->origname)) {
-    db_query(
-      "UPDATE {files} SET filename = '%s', filepath = '%s', origname = '%s' WHERE fid = %d",
-      $file['filename']['new'], $file['field']['filepath'], $file['filename']['old'], $file['field']['fid']
-    );
-  }
-
-  // Update 'filename'.
-  else {
-    db_query(
-      "UPDATE {files} SET filename = '%s', filepath = '%s' WHERE fid = %d",
-      $file['filename']['new'], $file['field']['filepath'], $file['field']['fid']
-    );
-  }
-
-  return TRUE;
-}
-
-function _filefield_paths_strip_path($path) {
-  $dirpath = file_directory_path();
-  $dirlen = drupal_strlen($dirpath);
-  if (drupal_substr($path, 0, $dirlen + 1) == $dirpath . '/') {
-    $path = drupal_substr($path, $dirlen + 1);
-  }
-  return $path;
-}
-
-function _filefield_paths_check_directory(&$directory, $mode = 0, $form_item = NULL) {
-  $directory = rtrim($directory, '/\\');
-
-  // error if dir is a file.
-  if (is_file($directory)) {
-    watchdog('file system',  'check_directory: The path  %directory is a file.',  array('%directory' => $directory), WATCHDOG_ERROR);
-    if ($form_item)  form_set_error($form_item, t('The directory %directory is a file!', array('%directory' => $directory)));
-    return FALSE;
-  }
-
-  // create the directory if it is missing.
-  if (!is_dir($directory) && $mode & FILE_CREATE_DIRECTORY && !@mkdir($directory, 0775, TRUE)) {
-    watchdog('file system', 'The directory %directory does not exist.', array('%directory' => $directory), WATCHDOG_ERROR);
-    if ($form_item) form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
-    return FALSE;
-  }
-
-  // Check to see if the directory is writable.
-  if (!is_writable($directory) && $mode & FILE_MODIFY_PERMISSIONS && !@chmod($directory, 0775)) {
-    watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
-    if ($form_item) form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
-    return FALSE;
-  }
-
-  if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
-    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
-    if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
-      fclose($fp);
-      chmod($directory . '/.htaccess', 0664);
-    }
-    else {
-      $repl = array('%directory' => $directory, '!htaccess' => nl2br(check_plain($htaccess_lines)));
-      form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl));
-      watchdog('security', "Security warning: Couldn't write .htaccess file.  Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl, WATCHDOG_ERROR);
-    }
-  }
-
-  return TRUE;
-}
+        }
+
+        // Convert field to lower case.
+        $field_data['form_path'][$name . '_cleanup'][$name . '_tolower'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Convert to lower case') . '.',
+          '#default_value' => isset($field['settings']['tolower'])
+            ? $field['settings']['tolower']
+            : 0
+          ,
+          '#description' => t('Convert !title to lower case', array('!title' => $field['title'])) . '.'
+        );
+
+        // Transliterate field with Transliteration module.
+        $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Transliterate') . '.',
+          '#default_value' => isset($field['settings']['transliterate'])
+            ? $field['settings']['transliterate']
+            : 0
+          ,
+          '#description' => t('Transliterate !title', array('!title' => $field['title'])) . '.'
+        );
+        if (!module_exists('transliteration')) {
+          $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate']['#disabled'] = TRUE;
+          $field_data['form_path'][$name . '_cleanup'][$name . '_transliterate']['#default_value'] = 0;
+        }
+
+        // Replacement patterns for field.
+        $field_data['form_path'][$name . '_tokens'] = array(
+          '#type' => 'fieldset',
+          '#title' => t('!title replacement patterns', array('!title' => $field['title'])),
+          '#collapsible' => TRUE,
+          '#collapsed' => TRUE,
+          '#description' => theme('filefield_paths_token_help'),
+          '#weight' => ($count - 1) * 3 + 2,
+          '#attributes' => array(
+            'class' => $name . ' tokens'
+          )
+        );
+      }
+
+      // Retroactive updates.
+      $field_data['form_path']['retroactive_update'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Retroactive update'),
+        '#description' => t('Move and rename previously uploaded files') . '.' .
+          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
+          t('This feature should only be used on developmental servers or with extreme caution') . '.',
+        '#weight' => 10
+      );
+
+      // Active updating.
+      $field_data['form_path']['active_updating'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Active updating'),
+        '#default_value' => variable_get("ffp_{$field_data['type']}_{$field_name}", 0),
+        '#description' => t('Actively move and rename previously uploaded files as required') . '.' .
+          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
+          t('This feature should only be used on developmental servers or with extreme caution') . '.',
+        '#weight' => 11
+      );
+
+      if (!in_array('filefield_paths_form_submit', $form['#submit'])) {
+        $form['#submit'][] = 'filefield_paths_form_submit';
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_submit().
+ */
+function filefield_paths_form_submit($form, &$form_state) {
+  $ffp = array();
+
+  // Invoke hook_filefield_paths_form_submit().
+  foreach (module_implements('filefield_paths_form_submit') as $module) {
+    $function = $module . '_filefield_paths_form_submit';
+    $function($form_state, $ffp);
+  }
+
+  if (count($ffp) > 0) {
+    $retroactive_update = FALSE;
+    $fields = module_invoke_all('filefield_paths_field_settings');
+    foreach ($ffp as $field_name => $field_data) {
+      $cols = $vals = $data = array();
+
+      foreach ($fields as $name => &$field) {
+        $field['settings'] = array(
+          'value' => $form_state['values']['ffp_' . $field_name][$name],
+          'tolower' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_tolower'],
+          'pathauto' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_pathauto'],
+          'transliterate' => $form_state['values']['ffp_' . $field_name][$name . '_cleanup'][$name . '_transliterate']
+        );
+
+        // Store additional settings from addon modules.
+        if (isset($field['data'])) {
+          foreach ($field['data'] as $key => $value) {
+            $field['settings'][$key] = $form_state['values']['ffp_' . $field_name][$value];
+          }
+        }
+
+        $cols[] = $field['sql'];
+        $vals[] = "'%s'";
+        $data[] = serialize($field['settings']);
+      }
+
+      $result = db_fetch_object(
+        db_query(
+          "SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'",
+          $field_data['type'], $field_name
+        )
+      );
+
+      // Update existing entry.
+      if (!empty($result)) {
+        foreach ($cols as &$col) {
+          $col .= " = '%s'";
+        }
+
+        db_query(
+          "UPDATE {filefield_paths} SET " . implode(', ', $cols) . " WHERE type = '%s' AND field = '%s'",
+          array_merge($data, array($field_data['type'], $field_name))
+        );
+      }
+
+      // Create new entry.
+      else {
+        db_query(
+          "INSERT INTO {filefield_paths} (type, field, " . implode(', ', $cols) . ") VALUES ('%s', '%s', " . implode(', ', $vals) . ")",
+          array_merge(array($field_data['type'], $field_name), $data)
+        );
+      }
+
+      if ($form_state['values']['ffp_' . $field_name]['retroactive_update']) {
+        $retroactive_update = TRUE;
+        $module = isset($form['#field']) ? $form['#field']['module'] : $field_name;
+        filefield_paths_batch_update($module, $field_name, arg(3));
+      }
+
+      variable_set("ffp_{$field_data['type']}_{$field_name}", $form_state['values']['ffp_' . $field_name]['active_updating']);
+    }
+
+    if ($retroactive_update) {
+      // Run batch.
+      batch_process($form_state['redirect']);
+    }
+  }
+}
+
+/**
+ * Set batch process to update FileField Paths.
+ *
+ * @param $module
+ *   The upload widgets module ('filefield', 'upload', etc).
+ * @param $field
+ *   The CCK field name - set as NULL if not applicable.
+ * @param $type
+ *   The Node type ('page', 'story', etc).
+ */
+function filefield_paths_batch_update($module, $field, $type) {
+  $objects = array();
+
+  // Invoke hook_filefield_paths_batch_update().
+  if (function_exists($function = $module . '_filefield_paths_batch_update')) {
+    $function($field, str_replace('-', '_', $type), $objects);
+  }
+
+  // Create batch.
+  $batch = array(
+    'title' => t('Updating FileField Paths'),
+    'operations' => array(
+      array('_filefield_paths_batch_update_process', array(array_unique($objects), $module, $field))
+    ),
+  );
+  batch_set($batch);
+}
+
+function _filefield_paths_batch_update_process($objects, $module, $field, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max'] = count($objects);
+    $context['sandbox']['objects'] = $objects;
+  }
+
+  // Process nodes by groups of 5.
+  $count = min(5, count($context['sandbox']['objects']));
+  for ($i = 1; $i <= $count; $i++) {
+    // For each oid, load the object, update the files and save it.
+    $oid = array_shift($context['sandbox']['objects']);
+
+    // Invoke hook_filefield_paths_update().
+    if (function_exists($function = $module . '_filefield_paths_update')) {
+      $function($oid, $field);
+    }
+
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Implements hook_nodeapi().
+ */
+function filefield_paths_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  $function = 'filefield_paths_node_'. $op;
+  if (function_exists($function)) {
+    $function($node);
+  }
+}
+
+/**
+ * Implements hook_node_presave().
+ */
+function filefield_paths_node_presave(&$node) {
+  if (module_exists('content')) {
+    $content_type = content_types($node->type);
+
+    foreach ($content_type['fields'] as $field) {
+      if ($field['type'] == 'filefield' && is_array($node->$field['field_name'])) {
+
+        foreach ($node->$field['field_name'] as $count => &$file) {
+          if (!is_array($file) || empty($file['filepath'])) {
+            continue;
+          }
+
+          // If file is newly uploaded, flag to be processed
+          if ($file['status'] == 0) {
+            $file['data']['process'] = TRUE;
+          }
+        }
+
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function filefield_paths_node_insert(&$node) {
+  filefield_paths_node_update($node);
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function filefield_paths_node_update(&$node) {
+  if (($ffp = filefield_paths_get_fields($node)) !== FALSE) {
+    $update = new stdClass;
+    $update->node = FALSE;
+
+    // Process files.
+    foreach ($ffp['#files'] as &$file) {
+      // Invoke hook_filefield_paths_process_file().
+      foreach (module_implements('filefield_paths_process_file') as $module) {
+        $function = $module . '_filefield_paths_process_file';
+        $function(($file['new'] || variable_get("ffp_{$node->type}_{$file['name']}", 0)), $file, $ffp['#settings'][$file['name']], $node, $update);
+      }
+    }
+
+    // Re-write node entry if required.
+    if ($update->node == TRUE) {
+      global $user;
+
+      drupal_write_record('node', $node, 'nid');
+      _node_save_revision($node, $user->uid, 'vid');
+    }
+
+    // Re-write cck fields.
+    if (module_exists('content')) {
+      if (module_exists('filefield')) {
+        _field_file_cache(NULL, TRUE);
+      }
+      _content_field_invoke_default('update', $node);
+      cache_clear_all('content:'. $node->nid . ':' . $node->vid, content_cache_tablename());
+    }
+
+    // Cleanup temporary paths.
+    if ($ffp['#settings']) {
+      foreach ($ffp['#settings'] as $name => $field) {
+        $paths = explode('/', $field['filepath']['value']);
+        filefield_paths_cleanup_temp($paths);
+
+        // Invoke hook_filefield_paths_cleanup().
+        foreach (module_implements('filefield_paths_cleanup') as $module) {
+          $function = $module . '_filefield_paths_cleanup';
+          $function($ffp, $paths, $name);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_content_fieldapi().
+ */
+function filefield_paths_content_fieldapi($op, $field) {
+  switch ($op) {
+    case 'delete instance':
+      db_query(
+        "DELETE FROM {filefield_paths} WHERE type = '%s' AND field = '%s'",
+        $field['type_name'], $field['field_name']
+      );
+      break;
+  }
+}
+
+function filefield_paths_cleanup_temp($paths) {
+  while ($paths) {
+    if (@rmdir(file_directory_path() . '/' . implode('/', $paths)) === TRUE) {
+      array_pop($paths);
+      continue;
+    }
+    break;
+  }
+}
+
+function filefield_paths_get_fields(&$node, $op = NULL) {
+  $ffp = array();
+
+  // Invoke hook_filefield_paths_get_fields().
+  foreach (module_implements('filefield_paths_get_fields') as $module) {
+    $function = $module . '_filefield_paths_get_fields';
+    $function($node, $ffp);
+  }
+
+  if (count($ffp) == 0 || (isset($ffp['#types']) && !is_array($ffp['#types']))) {
+    return FALSE;
+  }
+
+  $fields = module_invoke_all('filefield_paths_field_settings');
+
+  // Load fields settings
+  foreach ($ffp['#types'] as $name => $temp) {
+    $result = db_fetch_object(
+      db_query("SELECT * FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $node->type, $name)
+    );
+
+    if (!empty($result)) {
+      foreach ($fields as $field) {
+        $ffp['#settings'][$name][$field['sql']] = unserialize($result->$field['sql']);
+      }
+    }
+  }
+
+  return $ffp;
+}
+
+/**
+ * Implements hook_filefield_paths_process_file().
+ */
+function filefield_paths_filefield_paths_process_file($new, &$file, $settings, &$node, &$update) {
+  if ($new) {
+    // Process filename
+    $file['filename']['old'] = $file['field']['filename'];
+    if (($file['filename']['new'] = $settings['filename']['value']) != '') {
+      $file['filename']['new'] = filefield_paths_process_string($file['filename']['new'], 'node', $node, $settings['filename']);
+      $file['filename']['new'] = filefield_paths_process_string($file['filename']['new'], 'field', array(0 => $file['field']), $settings['filename']);
+    }
+    else {
+      $file['filename']['new'] = $file['field']['filename'];
+    }
+  
+    // Process filepath
+    $file['filepath']['old'] = $file['field']['filepath'];
+    $file['filepath']['new'] = filefield_paths_process_string(file_directory_path() . '/' . $settings['filepath']['value'] . '/' . $file['filename']['new'], 'node', $node, $settings['filepath']);
+    $file['filepath']['new'] = filefield_paths_process_string($file['filepath']['new'], 'field', array(0 => $file['field']), $settings['filepath']);
+
+    // Finalize files if necessary
+    if (dirname($file['filepath']['new']) != dirname($file['field']['filepath']) || $file['filename']['new'] != $file['field']['filename']) {
+      if (filefield_paths_file_move($file)) {
+
+        // Fix reference to old paths.
+        if (isset($node->body) || module_exists('content')) {
+          $file['filepath']['new'] = str_replace($file['filename']['old'], $file['filename']['new'], $file['filepath']['new']);
+          $file_directory_path = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC ? file_directory_path() : 'system/files';
+
+          // Regular expression to replace old file reference.
+          $pattern = array(
+            'regex' => str_replace('/', '\/', $file_directory_path) . '([^"]*?)' . str_replace('/', '\/', str_replace(file_directory_path(), '', $file['filepath']['old'])),
+            'regex_enc' => str_replace('/', '\/', drupal_urlencode($file_directory_path)) . '([^"]*?)' . str_replace('/', '\/', str_replace(drupal_urlencode(file_directory_path()), '', drupal_urlencode($file['filepath']['old']))),
+            'replace' => $file_directory_path . '$1' . str_replace(file_directory_path(), '', $file['filepath']['new']),
+          );
+
+          // Process regular expression.
+          _filefield_paths_replace_pattern($pattern, $node, $update);
+        }
+
+        // Store new filename in file Array
+        $file['field']['filename'] = $file['filename']['new'];
+      }
+    }
+  }
+}
+
+/**
+ * Run regular expression over all available text-based fields.
+ */
+function _filefield_paths_replace_pattern($pattern, &$node, &$update) {
+  $fields = _filefield_paths_text_fields($node);
+  // Loop through all fields and process filter.
+  foreach ($fields as $field => &$data) {
+    ${$field} = (preg_match('/' . $pattern['regex'] . '/s', $data))
+      ? preg_replace('/' . $pattern['regex'] . '/s', $pattern['replace'], $data)
+      : preg_replace('/' . $pattern['regex_enc'] . '/s', $pattern['replace'], $data);
+
+    if (${$field} != $data) {
+      $data = ${$field};
+      // Mark node for update.
+      if ($field == 'body' || $field == 'teaser') {
+        $update->node = TRUE;
+      }
+    }
+  }
+}
+
+/**
+ * Get all text-based fields attached to node.
+ */
+function _filefield_paths_text_fields($node) {
+  $fields = array();
+  // Get Node fields.
+  if (isset($node->body)) {
+    $fields += array(
+      'body' => &$node->body,
+      'teaser' => &$node->teaser,
+    );
+  }
+
+  // Get CCK fields.
+  if (module_exists('content')) {
+    $content_type = content_types($node->type);
+    foreach ($content_type['fields'] as $name => $field) {
+      if ($field['type'] == 'text' && is_array($node->{$name})) {
+        foreach ($node->{$name} as $key => $value) {
+          if (isset($node->{$name}[$key]['value'])) {
+            $fields["{$name}_{$key}"] = &$node->{$name}[$key]['value'];
+          }
+        }
+      }
+    }
+  }
+
+  return $fields;
+}
+
+/**
+ * Implements hook_token_list().
+ */
+function filefield_paths_token_list($type = 'all') {
+  if ($type == 'field' || $type == 'all') {
+    $tokens = array();
+    if (!module_exists('filefield')) {
+      $tokens['file']['filefield-onlyname'] = t("File name without extension");
+      $tokens['file']['filefield-extension'] = t("File extension");
+    }
+    $tokens['file']['filefield-onlyname-original'] = t("File name without extension - original");
+    $tokens['file']['filefield-extension-original'] = t("File extension - original");
+    // Tokens for Image module integration.
+    if (module_exists('image')) {
+      $tokens['image']['image-derivative'] = t("Image derivative (thumbnail, preview, etc)");
+    }
+    return $tokens;
+  }
+}
+
+/**
+ * Implements hook_token_values().
+ */
+function filefield_paths_token_values($type, $object = NULL) {
+  if ($type == 'field') {
+    $item = pathinfo($object[0]['filename']);
+    $tokens = array();
+    if (!module_exists('filefield')) {
+      // PHP < 5.2: pathinfo() doesn't return 'filename' variable.
+      $tokens['filefield-onlyname'] = isset($item['filename'])
+        ? $item['filename']
+        : basename($object[0]['filename'], '.' . $item['extension']);
+      $tokens['filefield-extension'] = $item['extension'];
+    }
+    // Original filename.
+    $orig = $item;
+    $filename = $object[0]['filename'];
+    $result = db_fetch_object(db_query("SELECT origname FROM {files} WHERE fid = %d", $object[0]['fid']));
+    if (!empty($result->origname)) {
+      $object[0]['origname'] = $result->origname;
+    }
+    if (!empty($object[0]['origname'])) {
+      $orig = pathinfo($object[0]['origname']);
+      $filename = $object[0]['origname'];
+    }
+    // PHP < 5.2: pathinfo() doesn't return 'filename' variable.
+    $tokens['filefield-onlyname-original'] = isset($orig['filename'])
+      ? $orig['filename']
+      : basename($filename, '.' . $orig['extension']);
+    $tokens['filefield-extension-original'] = $orig['extension'];
+    // Tokens for Image module integration.
+    if (module_exists('image')) {
+      $tokens['image-derivative'] = (isset($object[0]['type']) && $object[0]['type'] !== '_original')
+        ? '.' . $object[0]['type']
+        : '';
+    }
+    return $tokens;
+  }
+}
+
+/**
+ * Process and cleanup strings.
+ */
+function filefield_paths_process_string($value, $type, $object, $settings = array()) {
+
+  // Process string tokens.
+  $placeholders = _filefield_paths_get_values($type, $object, (module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto']));
+  $value = str_replace($placeholders['tokens'], $placeholders['values'], $value);
+
+  // Transliterate string.
+  if (module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate']) {
+    $value = transliteration_get($value);
+    if ($type == 'field') {
+      $paths = explode('/', $value);
+      foreach ($paths as &$path) {
+        $path = transliteration_clean_filename($path);
+      }
+
+      $value = implode('/', $paths);
+    }
+  }
+
+  // Convert string to lower case.
+  if ((isset($settings['tolower']) && $settings['tolower']) || (isset($settings['pathauto']) && $settings['pathauto'] && variable_get('pathauto_case', 0))) {
+    // Convert string to lower case
+    $value = drupal_strtolower($value);
+  }
+
+  // Ensure that there are no double-slash sequences due to empty token values.
+  $value = preg_replace('/\/+/', '/', $value);
+
+  return $value;
+}
+
+function _filefield_paths_get_values($type, $object, $pathauto = FALSE) {
+  switch ($type) {
+    case 'node':
+      $full = token_get_values($type, $object, TRUE);
+      break;
+
+    case 'field':
+      $all = filefield_paths_token_values($type, $object);
+      if (function_exists('filefield_token_values')) {
+        $all = array_merge(filefield_token_values($type, $object), $all);
+      }
+
+      $full = new stdClass();
+      $full->tokens = array_keys($all);
+      $full->values = array_values($all);
+      break;
+  }
+
+  $full->tokens = token_prepare_tokens($full->tokens);
+  if ($pathauto) {
+    $temp = clone $full;
+
+    // Strip square brackets from tokens for Pathauto.
+    foreach ($temp->tokens as &$token) {
+      $token = trim($token, "[]");
+    }
+
+    if (!function_exists('pathauto_clean_token_values')) {
+      module_load_include('inc', 'pathauto');
+    }
+    $full->values = pathauto_clean_token_values($temp);
+  }
+
+  return array('tokens' => $full->tokens, 'values' => $full->values);
+}
+
+/**
+ * Move file and update its database record.
+ */
+function filefield_paths_file_move(&$file, $replace = FILE_EXISTS_RENAME) {
+  $dest = _filefield_paths_strip_path(dirname($file['filepath']['new']));
+
+  foreach (explode('/', $dest) as $dir) {
+    $dirs[] = $dir;
+    $path = file_create_path(implode($dirs, '/'));
+    if (!_filefield_paths_check_directory($path, FILE_CREATE_DIRECTORY)) {
+      watchdog('filefield_paths', 'FileField Paths failed to create directory (%d).', array('%d' => $path), WATCHDOG_ERROR);
+      return FALSE;
+    }
+  }
+
+  if (!file_move($file['field']['filepath'], $dest . '/' . $file['filename']['new'], $replace)) {
+    watchdog('filefield_paths', 'FileField Paths failed to move file (%o) to (%n).', array('%o' => $file['filepath']['old'], '%n' => $dest .'/'. $file['filename']['new']), WATCHDOG_ERROR);
+    return FALSE;
+  }
+
+  $result = db_fetch_object(db_query("SELECT origname FROM {files} WHERE fid = %d", $file['field']['fid']));
+
+  // Set 'origname' and update 'filename'.
+  if (empty($result->origname)) {
+    db_query(
+      "UPDATE {files} SET filename = '%s', filepath = '%s', origname = '%s' WHERE fid = %d",
+      $file['filename']['new'], $file['field']['filepath'], $file['filename']['old'], $file['field']['fid']
+    );
+  }
+
+  // Update 'filename'.
+  else {
+    db_query(
+      "UPDATE {files} SET filename = '%s', filepath = '%s' WHERE fid = %d",
+      $file['filename']['new'], $file['field']['filepath'], $file['field']['fid']
+    );
+  }
+
+  return TRUE;
+}
+
+/**
+ * Implementation of hook_features_api().
+ * 
+ * Main info hook that features uses to determine what components are provided
+ * 
+ * The 'component' for this module is named 'filefield_paths_item' not just
+ * 'filefield_paths' to follow recommended practice documented in features.api
+ * 
+ * We export individual filefield_paths_item instances, although seldom on their
+ * own, usually as part of a bigger package. When a content type or cck field is
+ * being exported, these settings come along for the ride.
+ */
+function filefield_paths_features_api() {
+  return array(
+    'filefield_paths_item' => array(
+      'name' => t('Filefield paths'),
+      'default_hook' => 'filefield_paths_item_default_items',
+      'feature_source' => TRUE,
+      'default_file' => FEATURES_DEFAULTS_INCLUDED,
+      'file' => drupal_get_path('module', 'filefield_paths') .'/filefield_paths.features.inc',
+    ),
+  );
+}
+
+function _filefield_paths_strip_path($path) {
+  $dirpath = file_directory_path();
+  $dirlen = drupal_strlen($dirpath);
+  if (drupal_substr($path, 0, $dirlen + 1) == $dirpath . '/') {
+    $path = drupal_substr($path, $dirlen + 1);
+  }
+  return $path;
+}
+
+function _filefield_paths_check_directory(&$directory, $mode = 0, $form_item = NULL) {
+  $directory = rtrim($directory, '/\\');
+
+  // error if dir is a file.
+  if (is_file($directory)) {
+    watchdog('file system',  'check_directory: The path  %directory is a file.',  array('%directory' => $directory), WATCHDOG_ERROR);
+    if ($form_item)  form_set_error($form_item, t('The directory %directory is a file!', array('%directory' => $directory)));
+    return FALSE;
+  }
+
+  // create the directory if it is missing.
+  if (!is_dir($directory) && $mode & FILE_CREATE_DIRECTORY && !@mkdir($directory, 0775, TRUE)) {
+    watchdog('file system', 'The directory %directory does not exist.', array('%directory' => $directory), WATCHDOG_ERROR);
+    if ($form_item) form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
+    return FALSE;
+  }
+
+  // Check to see if the directory is writable.
+  if (!is_writable($directory) && $mode & FILE_MODIFY_PERMISSIONS && !@chmod($directory, 0775)) {
+    watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
+    if ($form_item) form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
+    return FALSE;
+  }
+
+  if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
+    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
+    if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
+      fclose($fp);
+      chmod($directory . '/.htaccess', 0664);
+    }
+    else {
+      $repl = array('%directory' => $directory, '!htaccess' => nl2br(check_plain($htaccess_lines)));
+      form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl));
+      watchdog('security', "Security warning: Couldn't write .htaccess file.  Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl, WATCHDOG_ERROR);
+    }
+  }
+
+  return TRUE;
+}
-- 
1.7.1

