? .git
Index: filefield.admin.inc
===================================================================
RCS file: filefield.admin.inc
diff -N filefield.admin.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ filefield.admin.inc	7 Jul 2008 22:47:49 -0000
@@ -0,0 +1,227 @@
+<?php
+// $Id$
+/**
+ * @file
+ * FileField: Defines a CCK file field type.
+ *
+ * Uses content.module to store the fid and field specific metadata,
+ * and Drupal's {files} table to store the actual file data.
+ *
+ * This file contains the forms and logic of the administration interface.
+ */
+
+/**
+ * Form callback for the formatter preset overview.
+ */
+function filefield_file_formatter_presets_form(&$form_state) {
+  $header = array(t('Formatter preset name'), t('Label'), t('Actions'));
+  $rows = array();
+  foreach (filefield_file_formatter_presets() as $preset) {
+    $row = array();
+    $row[] = check_plain($preset['preset_name']);
+    $row[] = check_plain($preset['label']);
+    $links = array();
+    $links[] = l(t('Edit'), 'admin/build/filefield/file-formatters/'. $preset['preset_id']);
+    if ($preset['preset_name'] != 'default') { // always keep this one at least
+      $links[] = l(t('Delete'), 'admin/build/filefield/file-formatters/'. $preset['preset_id'] .'/delete');
+    }
+    $row[] = implode('&nbsp;&nbsp;&nbsp;&nbsp;', $links);
+    $rows[] = $row;
+  }
+  $table = theme('table', $header, $rows);
+
+  $form = array();
+  $form['description'] = array(
+    '#type' => 'markup',
+    '#value' => t('The formatter presets that you can manage here will show up as CCK formatters for file fields on the "Display fields" tab of each content type.'),
+  );
+  $form['table'] = array(
+    '#type' => 'markup',
+    '#value' => $table,
+  );
+  return $form;
+}
+
+/**
+ * Form callback for configuring a single formatter preset (which in itself
+ * consists of a set of file formatters).
+ */
+function filefield_file_formatter_preset_settings_form(&$form_state, $preset = array()) {
+  if (is_string($preset) && $preset == 'add') {
+    $preset = array('preset_id' => 0, 'preset_name' => '',
+                    'label' => '', 'file_formatters' => array());
+  }
+
+  if (empty($preset['preset_id'])) {
+    $form['preset_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Preset name'),
+      '#description' => t('Assign a unique name of this preset, consisting only of lowercase unaccented letters (a-z), numbers (0-9), and underscores. The length of the name is limited to no more than 32 letters. This name cannot be changed anymore after the formatter is saved.'),
+      '#default_value' => '',
+      '#size' => 40,
+      '#maxlength' => 32,
+    );
+  }
+  else {
+    $form['preset_name'] = array(
+      '#type' => 'value',
+      '#value' => $preset['preset_name'],
+    );
+  }
+
+  $form['label'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Label'),
+    '#description' => t('The user visible name of this preset as it appears in the "Display fields" tab of content types, and in the "List" tab of the FileField file formatter preset management form.'),
+    '#default_value' => $preset['label'],
+    '#size' => 40,
+    '#maxlength' => 255,
+  );
+
+  $file_formatter_info = _filefield_file_formatter_info($preset['file_formatters']);
+
+  // Hidden info for the validate and submit callbacks.
+  $form['preset_id'] = array(
+    '#type' => 'value',
+    '#value' => $preset['preset_id'],
+  );
+  $form['file_formatter_info'] = array(
+    '#type' => 'value',
+    '#value' => $file_formatter_info,
+  );
+
+  $form['file_formatters'] = array(
+    '#title' => t('File display'),
+    '#description' => t('Control how files may be displayed in the node view and other views for this field. If no formatters are enabled or are able to handle a file then that specific file will not be displayed. You can also reorder the formatters to specify their priority: the top-most enabled formatter always gets to display the files that it supports, whereas the bottom-most enabled formatter only gets to handle them if the file is not supported by any other other one.'),
+    '#weight' => 5,
+    '#settings_type' => 'formatters', // info for the theme function
+  );
+  $form['file_formatters'] = _filefield_draggable_settings_table(
+    $form['file_formatters'], $file_formatter_info,
+    $field['file_formatters'], 'file_formatter_settings'
+  );
+
+  $form['buttons'] = array(
+    '#weight' => 50,
+  );
+  $form['buttons']['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save preset'),
+    '#weight' => 5,
+    '#validate' => array('filefield_file_formatter_preset_settings_validate'),
+    '#submit' => array('filefield_file_formatter_preset_settings_save'),
+  );
+
+  if (!empty($preset['preset_id']) && $preset['preset_name'] != 'default') {
+    $form['buttons']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#weight' => 10,
+      '#submit' => array('filefield_file_formatter_preset_settings_delete'),
+    );
+  }
+  return $form;
+}
+
+/**
+ * Validation callback for a specific formatter preset configuration form.
+ */
+function filefield_file_formatter_preset_settings_validate($form, &$form_state) {
+  // If we've got a new preset, make sure the preset id meets our expectations.
+  if (empty($form_state['values']['preset_id'])) {
+    $preset_name = $form_state['values']['preset_name'];
+
+    if (empty($preset_name) || drupal_strlen($preset_name) > 32
+        || !preg_match('!^[a-z0-9_]+$!', $preset_name)) {
+      form_set_error('preset_name', t('You need to assign a unique name to this preset, consisting only of lowercase unaccented letters (a-z), numbers (0-9), and underscores. The length of the name is limited to no more than 32 letters.'));
+    }
+
+    $existing_preset = _filefield_file_formatter_preset_by_name($preset_name);
+    if (!empty($existing_preset)) {
+      form_set_error('preset_name', t('The preset name %presetname is already in use by another preset, please choose another name for this one.', array('%presetname' => $preset_name)));
+    }
+  }
+
+  if (empty($form_state['values']['label'])) {
+      form_set_error('preset_name', t('The "Label" field may not be empty.'));
+  }
+
+  // Let modules add their own formatter specific validations.
+  $settings = $form_state['values']['file_formatters'];
+  $file_formatter_info = $form_state['values']['file_formatter_info'];
+
+  foreach ($file_formatter_info as $file_formatter => $info) {
+    $file_formatter_settings = isset($settings[$file_formatter])
+                                ? $settings[$file_formatter]
+                                : array();
+    module_invoke(
+      $info['module'], 'file_formatter_settings_'. $info['name'],
+      'validate', $file_formatter_settings
+    );
+  }
+}
+
+/**
+ * Save callback for a specific formatter preset configuration form.
+ */
+function filefield_file_formatter_preset_settings_save($form, &$form_state) {
+  $preset = array(
+    'preset_name'     => $form_state['values']['preset_name'],
+    'label'           => $form_state['values']['label'],
+    'file_formatters' => $form_state['values']['file_formatters'],
+  );
+  if ($form_state['values']['preset_id'] != 0) {
+    $preset['preset_id'] = $form_state['values']['preset_id'];
+  }
+  filefield_file_formatter_preset_save($preset);
+  $form_state['redirect'] = array('admin/build/filefield/file-formatters');
+}
+
+/**
+ * 'Delete' button submit callback, copied from node_form_delete_submit().
+ */
+function filefield_file_formatter_preset_settings_delete($form, &$form_state) {
+  $destination = '';
+  if (isset($_REQUEST['destination'])) {
+    $destination = drupal_get_destination();
+    unset($_REQUEST['destination']);
+  }
+  $form_state['redirect'] = array(
+    'admin/build/filefield/file-formatters/'. $form_state['values']['preset_id'] .'/delete',
+    $destination
+  );
+}
+
+/**
+ * 'Delete' confirm form callback.
+ */
+function filefield_file_formatter_preset_delete_form($form_state, $preset = array()) {
+  if (empty($preset)) {
+    drupal_set_message(t('The specified preset was not found.'), 'error');
+    drupal_goto('admin/build/filefield/file-formatters');
+  }
+  $form = array();
+  $form['preset_id'] = array('#type' => 'value', '#value' => $preset['preset_id']);
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the preset %presetname?',
+      array('%presetname' => $preset['preset_name'])
+    ),
+    'admin/build/filefield/file-formatters',
+    t('This action cannot be undone.'),
+    t('Delete'), t('Cancel')
+  );
+}
+
+/**
+ * 'Delete' confirm form submit callback: Delete the preset.
+ */
+function filefield_file_formatter_preset_delete_form_submit($form, &$form_state) {
+  $preset = filefield_file_formatter_preset($form_state['values']['preset_id']);
+  filefield_file_formatter_preset_delete($preset);
+  drupal_set_message(t('Preset %presetname deleted.', array(
+    '%presetname' => $preset['preset_name']
+  )));
+  $form_state['redirect'] = 'admin/build/filefield/file-formatters';
+}
Index: filefield.formatter.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/filefield/filefield.formatter.inc,v
retrieving revision 1.1
diff -u -p -r1.1 filefield.formatter.inc
--- filefield.formatter.inc	29 Jun 2008 20:04:34 -0000	1.1
+++ filefield.formatter.inc	7 Jul 2008 22:47:49 -0000
@@ -11,12 +11,15 @@
  */
 
 /**
- * Theme function for the 'default' filefield formatter.
+ * CCK formatter callback for all filefield formatters,
+ * delegating to the appropriate file formatter preset.
  */
-function theme_filefield_formatter_default($element) {
+function filefield_formatter($element) {
   $file = $element['#item'];
   $field = content_fields($element['#field_name']);
-  return theme('filefield', $file, $field);
+  $preset_name = $element['#formatter'];
+
+  return theme('filefield', $file, $field, $preset_name);
 }
 
 /**
@@ -26,9 +29,9 @@ function theme_filefield_formatter_defau
  *
  * This function checks if the file may be shown and returns an empty string
  * if viewing the file is not allowed for any reason. If you need to display it
- * in any case, please use theme('filefield') instead.
+ * in any case, please use theme('filefield_unguarded') instead.
  */
-function theme_filefield($file, $field) {
+function theme_filefield($file, $field, $preset_name = 'default') {
   if (!filefield_view_access($field['field_name'])) {
     return '';
   }
@@ -38,7 +41,7 @@ function theme_filefield($file, $field) 
   if (empty($file['list'])) {
     return '';
   }
-  return theme('filefield_unguarded', $file, $field);
+  return theme('filefield_unguarded', $file, $field, $preset_name);
 }
 
 /**
@@ -50,13 +53,13 @@ function theme_filefield($file, $field) 
  * in any case (except if the file doesn't exist at all). If you need to check
  * permissions, please use theme('filefield_guarded') instead.
  */
-function theme_filefield_unguarded($file, $field) {
+function theme_filefield_unguarded($file, $field, $preset_name = 'default') {
   if (empty($file['fid']) || !is_file($file['filepath'])) {
     return '';
   }
   drupal_add_css(drupal_get_path('module', 'filefield') .'/filefield.css');
 
-  $file_formatter_info = filefield_formatter_for_file($file, $field);
+  $file_formatter_info = filefield_formatter_for_file($file, $field, $preset_name);
   if (empty($file_formatter_info)) {
     return '<div class="filefield-item filefield-item-empty"/>';
   }
@@ -64,12 +67,9 @@ function theme_filefield_unguarded($file
   foreach ($file_formatter_info['css'] as $css_path) {
     drupal_add_css($css_path);
   }
-  $settings = isset($field['file_formatters'][$file_formatter_info['key']])
-    ? $field['file_formatters'][$file_formatter_info['key']]
-    : NULL;
 
   return '<div class="filefield-item">'.
-    theme($file_formatter_info['theme'], (object)$file, $field, $settings)
+    theme($file_formatter_info['theme'], (object)$file, $field, $file_formatter_info['settings'])
     .'</div>';
 }
 
Index: filefield.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/filefield/filefield.install,v
retrieving revision 1.7
diff -u -p -r1.7 filefield.install
--- filefield.install	28 Jun 2008 06:57:07 -0000	1.7
+++ filefield.install	7 Jul 2008 22:47:49 -0000
@@ -8,19 +8,83 @@
  * and Drupal's {files} table to store the actual file data.
  */
 
-include_once(drupal_get_path('module', 'filefield') .'/field_file.inc');
+include_once(drupal_get_path('module', 'filefield') .'/filefield.module');
+
+/**
+ * Implementation of hook_schema.
+ */
+function filefield_schema() {
+  $schema['filefield_file_formatter_preset'] = array(
+    'description' => t('A table that stores file formatter presets and their settings.'),
+    'fields' => array(
+      'preset_id' => array(
+        'description' => t('The primary identifier for a preset.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'preset_name' => array(
+        'description' => t('The unique name ([a-z], [0-9] and underscores allowed) for a preset. Used in formatter names, required by theme functions.'),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+      ),
+      'label' => array(
+        'description' => t('The user visible name of the preset, to be shown as formatter choices and whatnot.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'file_formatters' => array(
+        'description' => t('A serialized array of all file formatter settings (keyed by $formatter_name, then individual settings) for this preset. No file formatter may expect any values in here but an empty array. Each formatter is assigned \'enabled\' and \'weight\' settings in addition to possible custom values defined by the formatter itself.'),
+        'type' => 'text',
+        'size' => 'medium',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+      ),
+    ),
+    'primary key' => array('preset_id'),
+    'unique keys' => array('unique_preset_name' => array('preset_name')),
+  );
+  return $schema;
+}
 
 /**
  * Implementation of hook_install().
  */
 function filefield_install() {
+  drupal_install_schema('filefield');
   content_notify('install', 'filefield');
+  _filefield_install_insert_default_file_formatter();
+}
+
+function _filefield_install_insert_default_file_formatter() {
+  $t = get_t(); // don't know when bootstrapping is done, let's just make sure
+
+  // The default preset - without settings, FileField defaults to
+  // displaying the generic file formatter only.
+  $preset = array(
+    'preset_name' => 'default',
+    'label' => $t('Link to file (with icon)'),
+    'file_formatters' => array(),
+  );
+  $ret = array(update_sql(
+    "INSERT INTO {filefield_file_formatter_preset}
+      (preset_name, label, file_formatters)
+      VALUES ('$preset[preset_name]', '$preset[label]',
+      '". serialize($preset['file_formatters']) ."')"
+  ));
+  // Register the formatter theme functions with the theme registry.
+  drupal_rebuild_theme_registry();
+
+  return $ret;
 }
 
 /**
  * Implementation of hook_uninstall().
  */
 function filefield_uninstall() {
+  drupal_uninstall_schema('filefield');
   content_notify('uninstall', 'filefield');
 }
 
@@ -118,3 +182,13 @@ function filefield_update_3() {
   db_query('DELETE FROM {cache_content}');
   return $ret;
 }
+
+/**
+ * Update to filefield 6.x-1.0-beta3:
+ * Create a table for file formatter presets, and a default value.
+ */
+function filefield_update_4() {
+  $ret = drupal_install_schema('filefield');
+  $ret = array_merge($ret, _filefield_install_insert_default_file_formatter());
+  return $ret;
+}
Index: filefield.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/filefield/filefield.module,v
retrieving revision 1.105
diff -u -p -r1.105 filefield.module
--- filefield.module	3 Jul 2008 17:38:39 -0000	1.105
+++ filefield.module	7 Jul 2008 22:47:50 -0000
@@ -23,6 +23,48 @@ include_once(drupal_get_path('module', '
 function filefield_menu() {
   $items = array();
 
+  // Admin forms for file formatter preset management.
+  $items['admin/build/filefield/file-formatters'] = array(
+    'title' => 'FileField formatters',
+    'description' => 'Manage file formatter presets that can be selected in the display settings of content types and views.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filefield_file_formatter_presets_form'),
+    'access arguments' => array('administer content types'),
+    'file' => 'filefield.admin.inc',
+  );
+  $items['admin/build/filefield/file-formatters/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/build/filefield/file-formatters/add'] = array(
+    'title' => 'Add new formatter preset',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filefield_file_formatter_preset_settings_form', 'add'),
+    'access arguments' => array('administer content types'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'filefield.admin.inc',
+  );
+  $items['admin/build/filefield/file-formatters/%filefield_file_formatter_preset'] = array(
+    'title callback' => 'filefield_file_formatter_preset_title_callback',
+    'title arguments' => array(t('Configure !presetname preset'), 4),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filefield_file_formatter_preset_settings_form', 4),
+    'access arguments' => array('administer content types'),
+    'type' => MENU_CALLBACK,
+    'file' => 'filefield.admin.inc',
+  );
+  $items['admin/build/filefield/file-formatters/%filefield_file_formatter_preset/delete'] = array(
+    'title callback' => 'filefield_file_formatter_preset_title_callback',
+    'title arguments' => array(t('Delete preset: !presetname'), 4),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filefield_file_formatter_preset_delete_form', 4),
+    'access arguments' => array('administer content types'),
+    'type' => MENU_CALLBACK,
+    'file' => 'filefield.admin.inc',
+  );
+
+  // JavaScript callbacks.
   $items['filefield/js/upload/%/%/%'] = array(
     'page callback' => 'filefield_js',
     'page arguments' => array(3, 4, 5, 'filefield_file_upload_js'),
@@ -43,6 +85,21 @@ function filefield_menu() {
 }
 
 /**
+ * Menu wildcard loader.
+ */
+function filefield_file_formatter_preset_load($preset_id) {
+  return filefield_file_formatter_preset($preset_id, TRUE);
+}
+
+function filefield_file_formatter_preset_title_callback($title, $preset = array()) {
+  $replacements = array();
+  if (!empty($preset)) {
+    $replacements['!presetname'] = check_plain($preset['preset_name']);
+  }
+  return strtr($title, $replacements);
+}
+
+/**
  * Access callback for the JavaScript upload and deletion AHAH callbacks.
  * The content_permissions module provides nice fine-grained permissions for
  * us to check, so we can make sure that the user may actually edit the file.
@@ -94,7 +151,7 @@ function filefield_elements() {
  * Implementation of hook_theme().
  */
 function filefield_theme() {
-  return array(
+  $theme = array(
     'filefield_draggable_settings_table' => array(
       'arguments' => array('element' => NULL),
       'file' => 'filefield.theme.inc',
@@ -119,16 +176,12 @@ function filefield_theme() {
       'arguments' => array('element' => NULL),
       //'file' => this one,
     ),
-    'filefield_formatter_default' => array(
-      'arguments' => array('element' => NULL),
-      'file' => 'filefield.formatter.inc',
-    ),
     'filefield_unguarded' => array(
-      'arguments' => array('file' => NULL, 'field' => NULL),
+      'arguments' => array('file' => NULL, 'field' => NULL, 'preset_name' => NULL),
       'file' => 'filefield.formatter.inc',
     ),
     'filefield' => array(
-      'arguments' => array('file' => NULL, 'field' => NULL),
+      'arguments' => array('file' => NULL, 'field' => NULL, 'preset_name' => NULL),
       'file' => 'filefield.formatter.inc',
     ),
     'filefield_file_formatter_generic' => array(
@@ -138,6 +191,14 @@ function filefield_theme() {
       'file' => 'filefield.formatter.inc',
     ),
   );
+  foreach (filefield_file_formatter_presets() as $preset) {
+    $theme['filefield_formatter_'. $preset['preset_name']] = array(
+      'arguments' => array('element' => NULL),
+      'file' => 'filefield.formatter.inc',
+      'function' => 'filefield_formatter',
+    );
+  }
+  return $theme;
 }
 
 /**
@@ -204,38 +265,13 @@ function filefield_field_settings($op, $
         '#default_value' => isset($field['force_list']) ? $field['force_list'] : 0,
         '#description' => t('If enabled, the "List" checkbox will be hidden and files are always shown. Otherwise, the user can choose for each file whether it should be listed or not.'),
       );
-
-      $form['file_formatters'] = array(
-        '#title' => t('File display'),
-        '#description' => t('Control how files may be displayed in the node view and other views for this field. If no formatters are enabled or are able to handle a file then that specific file will not be displayed. You can also reorder the formatters to specify their priority: the top-most enabled formatter always gets to display the files that it supports, whereas the bottom-most enabled formatter only gets to handle them if the file is not supported by any other other one.'),
-        '#weight' => 5,
-        '#settings_type' => 'formatters', // info for the theme function
-      );
-      $file_formatter_info = _filefield_file_formatter_info($field);
-
-      $form['file_formatters'] = _filefield_draggable_settings_table(
-        $form['file_formatters'], $file_formatter_info,
-        $field['file_formatters'], 'file_formatter_settings'
-      );
       return $form;
 
     case 'validate':
-      // Let modules add their own formatter specific validations.
-      $file_formatter_info = _filefield_file_formatter_info($widget);
-
-      foreach ($file_formatter_info as $file_formatter => $info) {
-        $file_formatter_settings = isset($field['file_formatters'][$file_formatter])
-                                    ? $field['file_formatters'][$file_formatter]
-                                    : array();
-        module_invoke(
-          $info['module'], 'file_formatter_settings_'. $info['name'],
-          'validate', $file_formatter_settings
-        );
-      }
       break;
 
     case 'save':
-      return array('force_list', 'file_formatters');
+      return array('force_list');
 
     case 'database columns':
       $columns = array(
@@ -367,17 +403,21 @@ function filefield_field($op, $node, $fi
   }
 }
 
+
 /**
  * Implementation of CCK's hook_field_formatter_info().
  */
 function filefield_field_formatter_info() {
-  return array(
-    'default' => array(
-      'label' => t('Dynamic file formatters'),
+  $formatters = array();
+
+  foreach (filefield_file_formatter_presets() as $preset) {
+    $formatters[$preset['preset_name']] = array(
+      'label' => $preset['label'],
       'field types' => array('file'),
       'multiple values' => CONTENT_HANDLE_CORE,
-    ),
-  );
+    );
+  }
+  return $formatters;
 }
 
 /**
@@ -463,8 +503,8 @@ function filefield_widget_settings($op, 
 
     case 'validate':
       $valid = FALSE;
-      foreach ($widget['file_widgets'] as $file_widget_name => $info) {
-        if ($info['enabled']) {
+      foreach ($widget['file_widgets'] as $file_widget_name => $widget_settings) {
+        if ($widget_settings['enabled']) {
           $valid = TRUE;
           break;
         }
@@ -477,12 +517,9 @@ function filefield_widget_settings($op, 
       $file_widget_info = _filefield_file_widget_info($widget);
 
       foreach ($file_widget_info as $file_widget_name => $info) {
-        $file_widget_settings = isset($widget['file_widgets'][$file_widget_name])
-                                ? $widget['file_widgets'][$file_widget_name]
-                                : array();
         module_invoke(
           $info['module'], 'file_widget_settings_'. $info['name'],
-          'validate', $file_widget_settings
+          'validate', $info['settings']
         );
       }
       break;
@@ -549,7 +586,7 @@ function _filefield_draggable_settings_t
       '#type' => 'checkbox',
       '#title' => $info['title'],
       '#description' => $info['description'],
-      '#default_value' => $info['enabled'],
+      '#default_value' => $info['settings']['enabled'],
     );
     $element[$extension_name]['weight'] = array(
       '#type' => 'weight',
@@ -558,11 +595,8 @@ function _filefield_draggable_settings_t
     );
 
     // Let modules add their own extension specific settings.
-    $file_extension_settings = isset($extension_settings[$extension_name])
-                                ? $extension_settings[$extension_name]
-                                : array();
     $additions = module_invoke($info['module'], $hook_base .'_'. $info['name'],
-                               'form', $file_extension_settings);
+                               'form', $info['settings']);
     if (is_array($additions)) {
       $element[$extension_name] = array_merge($element[$extension_name], $additions);
     }
@@ -596,7 +630,7 @@ function _filefield_add_css($widget_or_f
  *
  * @return
  *   An array with info about the most appropriate file widget,
- *   or NULL if no widget is available to edit this file.
+ *   or FALSE if no widget is available to edit this file.
  */
 function filefield_widget_for_file($file, $field, $field_widget) {
   $file_widget_info = _filefield_file_widget_info($field_widget);
@@ -611,10 +645,15 @@ function filefield_widget_for_file($file
  *
  * @return
  *   An array with info about the most appropriate file formatter,
- *   or NULL if no formatter is available to display this file.
+ *   or FALSE if no formatter is available to display this file (or if
+ *   no preset exists for the given preset name or id).
  */
-function filefield_formatter_for_file($file, $field) {
-  $file_formatter_info = _filefield_file_formatter_info($field);
+function filefield_formatter_for_file($file, $field, $preset_id) {
+  $preset = filefield_file_formatter_preset($preset_id);
+  if (!$preset) {
+    return FALSE;
+  }
+  $file_formatter_info = _filefield_file_formatter_info($preset['file_formatters']);
   $file = (object) $file; // other modules only get to see objects
   $suitability_args = array($file, $field);
   return _filefield_extension_for_file($file_formatter_info, $suitability_args);
@@ -627,7 +666,7 @@ function filefield_formatter_for_file($f
 function _filefield_extension_for_file($file_extension_info, $suitability_args) {
   $suitable_extension_info = array();
   foreach ($file_extension_info as $extension_name => $info) {
-    if (!$info['enabled']) {
+    if (!$info['settings']['enabled']) {
       continue; // the admin disabled this widget or formatter
     }
     $handles_file = $info['suitability callback'];
@@ -643,7 +682,7 @@ function _filefield_extension_for_file($
     $suitable_extension_info[] = $info;
   }
   // Return the most appropriate widget/formatter, if one was found.
-  return empty($suitable_extension_info) ? NULL : reset($suitable_extension_info);
+  return empty($suitable_extension_info) ? FALSE : reset($suitable_extension_info);
 }
 
 /**
@@ -663,9 +702,9 @@ function _filefield_file_widget_info($fi
  * This function also sorts the formatters in the way that the administrator
  * has specified for this field.
  */
-function _filefield_file_formatter_info($field) {
+function _filefield_file_formatter_info($file_formatter_settings) {
   $file_formatter_info = _filefield_file_extension_info_original('formatter');
-  return _filefield_file_extension_info($file_formatter_info, $field['file_formatters']);
+  return _filefield_file_extension_info($file_formatter_info, $file_formatter_settings);
 }
 
 /**
@@ -677,24 +716,21 @@ function _filefield_file_formatter_info(
 function _filefield_file_extension_info($file_extension_info, $settings) {
   // Sort and enable the formatters according to previous admin settings or defaults.
   foreach ($file_extension_info as $extension_name => $info) {
-    if (isset($settings[$extension_name]['weight'])) {
-      $info['weight'] = $settings[$extension_name]['weight'];
-    }
-    else {
+    $info['settings'] = isset($settings[$extension_name])
+                        ? $settings[$extension_name] : array();
+
+    if (!isset($info['settings']['weight'])) {
       // By default, the generic file widget/formatter should be last in the
       // list of possible formatters, and other new formatters should also not
       // be preferred to ones that already had their weight configured before.
-      $info['weight'] = ($extension_name == 'filefield_generic') ? 1000 : 999;
+      $info['settings']['weight'] = ($extension_name == 'filefield_generic') ? 1000 : 999;
     }
 
-    if (isset($settings[$extension_name]['enabled'])) {
-      $info['enabled'] = $settings[$extension_name]['enabled'];
-    }
-    else {
+    if (!isset($info['settings']['enabled'])) {
       // By default, enable only the generic file widget/formatter, so that
       // newly enabled modules don't show their widges/formatters without
       // approval of the admin.
-      $info['enabled'] = ($extension_name == 'filefield_generic');
+      $info['settings']['enabled'] = ($extension_name == 'filefield_generic');
     }
     $file_extension_info[$extension_name] = $info;
   }
@@ -736,7 +772,7 @@ function _filefield_file_extension_info_
 function _filefield_sort_by_weight($items) {
   uasort($items, '_filefield_sort_by_weight_helper');
   foreach ($items as $delta => $item) {
-    unset($items[$delta]['weight']);
+    unset($items[$delta]['settings']['weight']);
   }
   return $items;
 }
@@ -746,14 +782,161 @@ function _filefield_sort_by_weight($item
  * (copied form element_sort(), which acts on #weight keys)
  */
 function _filefield_sort_by_weight_helper($a, $b) {
-  $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0;
-  $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0;
+  $a_weight = (is_array($a) && isset($a['settings']['weight'])) ? $a['settings']['weight'] : 0;
+  $b_weight = (is_array($b) && isset($b['settings']['weight'])) ? $b['settings']['weight'] : 0;
   if ($a_weight == $b_weight) {
     return 0;
   }
   return ($a_weight < $b_weight) ? -1 : 1;
 }
 
+/**
+ * Get an array of all file formatter presets and their settings.
+ *
+ * @param $reset
+ *   If set to TRUE, it will clear the preset cache.
+ *
+ * @return
+ *   An array of presets, with preset ids as array keys. Each preset contains
+ *   the properties 'preset_id', 'preset_name', 'label' and 'file_formatters'
+ *   in their respective array elements.
+ */
+function filefield_file_formatter_presets($reset = FALSE) {
+  static $presets = array();
+
+  // Clear caches if $reset is true.
+  if ($reset) {
+    $presets = array();
+    cache_clear_all('filefield_file_formatter_presets', 'cache');
+
+    // Clear the content.module cache (refreshes its list of formatters).
+    if (module_exists('content')) {
+      content_clear_type_cache();
+    }
+  }
+  // Return presets if the array is populated.
+  if (!empty($presets)) {
+    return $presets;
+  }
+
+  // Grab from cache or build the array.
+  if ($cache = cache_get('filefield_file_formatter_presets', 'cache')) {
+    $presets = $cache->data;
+  }
+  else {
+    $result = db_query('SELECT preset_id, preset_name, label, file_formatters
+                        FROM {filefield_file_formatter_preset}
+                        ORDER BY preset_name');
+    while ($preset = db_fetch_array($result)) {
+      $preset['file_formatters'] = unserialize($preset['file_formatters']);
+      $presets[$preset['preset_id']] = $preset;
+    }
+    cache_set('filefield_file_formatter_presets', $presets);
+  }
+  return $presets;
+}
+
+/**
+ * Load a file formatter preset by its preset id.
+ *
+ * @param $preset_id_or_name
+ *   Either the numeric id of the preset or its unique string identifier.
+ * @param $reset
+ *   If set to TRUE, it will clear the preset cache.
+ *
+ * @return
+ *   A preset array, containing the properties 'preset_id', 'preset_name',
+ *   'label' and 'file_formatters' in their respective array elements.
+ *   If there is no preset with the given preset id or name, an empty array
+ *   is returned.
+ */
+function filefield_file_formatter_preset($preset_id_or_name, $reset = FALSE) {
+  return is_numeric($preset_id_or_name)
+          ? _filefield_file_formatter_preset_by_id($preset_id_or_name, $reset)
+          : _filefield_file_formatter_preset_by_name($preset_id_or_name, $reset);
+}
+
+/**
+ * Load a file formatter preset by its preset id.
+ *
+ * @param $preset_id
+ *   The numeric id of the preset.
+ * @param $reset
+ *   If set to TRUE, it will clear the preset cache.
+ *
+ * @return
+ *   A preset array, containing the properties 'preset_id', 'preset_name',
+ *   'label' and 'file_formatters' in their respective array elements.
+ *   If there is no preset with the given preset id, an empty array is returned.
+ */
+function _filefield_file_formatter_preset_by_id($preset_id, $reset = FALSE) {
+  $presets = filefield_file_formatter_presets($reset);
+  return (isset($presets[$preset_id])) ? $presets[$preset_id] : array();
+}
+
+/**
+ * Load a file formatter preset by its preset name.
+ *
+ * @param $preset_name
+ *   The unique string identifier of the preset.
+ * @param $reset
+ *   If set to TRUE, it will clear the preset cache.
+ *
+ * @return
+ *   A preset array, containing the properties 'preset_id', 'preset_name',
+ *   'label' and 'file_formatters' in their respective array elements.
+ *   If there is no preset with the given preset id, an empty array is returned.
+ */
+function _filefield_file_formatter_preset_by_name($preset_name, $reset = FALSE) {
+  static $presets_by_name = array();
+  if (empty($presets_by_name) && $presets = filefield_file_formatter_presets($reset)) {
+    foreach ($presets as $preset) {
+      $presets_by_name[$preset['preset_name']] = $preset;
+    }
+  }
+  return (isset($presets_by_name[$preset_name]))
+          ? $presets_by_name[$preset_name]
+          : array();
+}
+
+/**
+ * Save a file formatter preset.
+ *
+ * @param $preset
+ *   A file formatter preset array.
+ *
+ * @return
+ *   The same preset array that was passed as argument. In the case of a
+ *   new preset, the 'preset_id' property will be populated afterwards.
+ */
+function filefield_file_formatter_preset_save($preset) {
+  if (isset($preset['preset_id']) && is_numeric($preset['preset_id'])) {
+    // existing preset, update
+    drupal_write_record('filefield_file_formatter_preset', $preset, 'preset_id');
+  }
+  else { // new preset, insert
+    drupal_write_record('filefield_file_formatter_preset', $preset);
+  }
+  filefield_file_formatter_presets(TRUE); // reset the preset cache
+
+  // Register the formatter theme functions with the theme registry.
+  drupal_rebuild_theme_registry();
+
+  return $preset;
+}
+
+/**
+ * Delete a file formatter preset.
+ *
+ * @param $preset
+ *   A file formatter preset array.
+ */
+function filefield_file_formatter_preset_delete($preset) {
+  db_query('DELETE FROM {filefield_file_formatter_preset}
+            WHERE preset_id = %d', $preset['preset_id']);
+  filefield_file_formatter_presets(TRUE); // reset the cache
+}
+
 
 /**
  * Implementation of filefield's hook_file_formatter_info().
