Add a file field to core for D7. http://drupal.org/node/391330

From: andrew morton <drewish@katherinehouse.com>


---

 .../modules/file_reference/file_reference.info     |    6 
 .../modules/file_reference/file_reference.module   |  299 ++++++++++++++++++++
 .../modules/file_reference/file_reference.test     |   35 ++
 3 files changed, 340 insertions(+), 0 deletions(-)
 create mode 100644 modules/field/modules/file_reference/file_reference.info
 create mode 100644 modules/field/modules/file_reference/file_reference.module
 create mode 100644 modules/field/modules/file_reference/file_reference.test


diff --git modules/field/modules/file_reference/file_reference.info modules/field/modules/file_reference/file_reference.info
new file mode 100644
index 0000000..c06f42f
--- /dev/null
+++ modules/field/modules/file_reference/file_reference.info
@@ -0,0 +1,6 @@
+; $Id$
+name = File
+description = Defines file field types.
+package = Core - fields
+core = 7.x
+files[]=file_reference.module
diff --git modules/field/modules/file_reference/file_reference.module modules/field/modules/file_reference/file_reference.module
new file mode 100644
index 0000000..d26cb4c
--- /dev/null
+++ modules/field/modules/file_reference/file_reference.module
@@ -0,0 +1,299 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines file field types.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function file_reference_theme() {
+  return array(
+    'file_reference_textfield' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_file_reference_default' => array(
+      'arguments' => array('element' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function file_reference_field_info() {
+  return array(
+    'file_reference' => array(
+      'label' => t('File'),
+      'description' => t('This field stores files in the database.'),
+      'settings' => array(),
+      'instance_settings' => array('file_reference_processing' => 0),
+      'default_widget' => 'file_reference_textfield',
+      'default_formatter' => 'file_reference_default',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_schema().
+ */
+function file_reference_field_columns($field) {
+  $columns = array(
+    'fid' => array(
+      'type' => 'int',
+      'not null' => FALSE
+    ),
+  );
+  return $columns;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function file_reference_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+  if (is_array($items)) {
+    foreach ($items as $delta => $item) {
+      $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+      if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+      if (!empty($item['fid'])) {
+        #TODO: file validation with file_reference_validate()?
+      }
+    }
+  }
+}
+
+function file_reference_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+  global $language;
+  foreach ($items as $delta => $item) {
+    #TODO: actually do something with files here?
+    if (!empty($instance['settings']['file_reference_processing'])) {
+      $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
+      $file = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : '';
+    }
+    else {
+      $text = check_plain($item['value']);
+    }
+    $items[$delta]['safe'] = $file;
+  }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function file_reference_field_is_empty($item, $field) {
+  if (empty($item['value']) && (string)$item['value'] !== '0') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function file_reference_field_formatter_info() {
+  return array(
+    'file_reference_default' => array(
+      'label' => t('Default'),
+      'field types' => array('file_reference'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme function for 'default' file field formatter.
+ */
+function theme_field_formatter_file_reference_default($element) {
+  return $element['#item']['safe'];
+}
+
+/**
+ * Implementation of hook_file_references().
+ */
+function file_file_references($file) {
+  // If we're using the file object is being used try to prevent other modules
+  // from deleting it.
+# TODO: Need to figure out how to query all our tables to search for the file id.
+//  $count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField();
+  $count = 0;
+  if ($count) {
+    // Return the name of the module and how many references it has to the file.
+    return array('file_reference' => $count);
+  }
+}
+
+/**
+ * Implementation of hook_file_delete().
+ */
+function file_file_delete($file) {
+  // Delete all information associated with the file.
+# TODO: Need to figure out how to query all our tables to search for the file id.
+//  db_delete('upload')->condition('fid', $file->fid)->execute();
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the field module will handle
+ * the default value and multiple values for these widgets.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
+ */
+function file_reference_field_widget_info() {
+  return array(
+    'file_reference_textfield' => array(
+      'label' => t('File reference field'),
+      'field types' => array('file_reference'),
+      'settings' => array('size' => 60),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of FAPI hook_elements().
+ *
+ * Any FAPI callbacks needed for individual widgets can be declared here,
+ * and the element will be passed to those callbacks for processing.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ *
+ * Autocomplete_path is not used by file_reference_field_widget but other
+ * widgets can use it (see nodereference and userreference).
+ */
+function file_reference_elements() {
+  return array(
+    'file_reference_textfield' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'),
+      '#delta' => 0,
+      '#process' => array('file_reference_textfield_process'),
+      '#autocomplete_path' => FALSE,
+      ),
+  );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_field_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * Field module will set the weight, field name and delta values
+ * for each form element.
+ *
+ * If there are multiple values for this field, the field module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ *   the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ *   the form_state, $form_state['values'][$field['field_name']]
+ *   holds the field's form values.
+ * @param $field
+ *   The field structure.
+ * @param $instance
+ *   the field instance array
+ * @param $items
+ *   array of default values for this field
+ * @param $delta
+ *   the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ *   the form item for a single element for this field
+ */
+function file_reference_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
+  $element = array(
+    '#type' => $instance['widget']['type'],
+    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+  );
+  return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ *
+ * TODO: For widgets to be actual FAPI 'elements', reusable outside of a
+ * 'field' context, they shoudn't rely on $field and $instance. The bits of
+ * information needed to adjust the behavior of the 'element' should be
+ * extracted in hook_field_widget() above.
+ */
+function file_reference_textfield_process($element, $edit, $form_state, $form) {
+  $field = $form['#fields'][$element['#field_name']]['field'];
+  $instance = $form['#fields'][$element['#field_name']]['instance'];
+  $field_key = $element['#columns'][0];
+  $delta = $element['#delta'];
+
+  $element[$field_key] = array(
+    '#type' => 'textfield',
+    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+    '#autocomplete_path' => $element['#autocomplete_path'],
+    '#size' => $instance['widget']['settings']['size'],
+    '#attributes' => array('class' => 'text'),
+    // The following values were set by the field module and need
+    // to be passed down to the nested element.
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => $element['#required'],
+    '#field_name' => $element['#field_name'],
+    '#bundle' => $element['#bundle'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+
+  $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
+
+  if (!empty($instance['settings']['file_reference_processing'])) {
+    $filter_key = $element['#columns'][1];
+    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+    $parents = array_merge($element['#parents'] , array($filter_key));
+    $element[$filter_key] = filter_form($format, 1, $parents);
+  }
+
+  // Used so that hook_field('validate') knows where to flag an error.
+  // TODO: rework that. See http://groups.drupal.org/node/18019.
+  $element['_error_element'] = array(
+    '#type' => 'value',
+    '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+  );
+
+  return $element;
+}
+
+/**
+ * FAPI theme for an individual text elements.
+ *
+ * The textfield or textarea is already rendered by the
+ * textfield or textarea themes and the html output
+ * lives in $element['#children']. Override this theme to
+ * make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta] is the position of this element in the group
+ */
+function theme_file_reference_textfield($element) {
+  return $element['#children'];
+}
+
+function theme_file_reference_textarea($element) {
+  return $element['#children'];
+}
diff --git modules/field/modules/file_reference/file_reference.test modules/field/modules/file_reference/file_reference.test
new file mode 100644
index 0000000..512c735
--- /dev/null
+++ modules/field/modules/file_reference/file_reference.test
@@ -0,0 +1,35 @@
+<?php
+// $Id$
+
+class FileFieldTestCase extends DrupalWebTestCase {
+  protected $instance;
+
+  function getInfo() {
+    return array(
+      'name'  => t('File Field'),
+      'description'  => t("Test the creation of image fields."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field', 'file', 'field_test');
+  }
+
+  // Test widgets.
+
+  /**
+   * Test filefield widget.
+   */
+  function testFilefieldWidget() {
+    // Create a field
+    $field = $this->drupalCreateField('text');
+    $this->instance = $this->drupalCreateFieldInstance($field['field_name'], 'file_textfield', 'file_default', FIELD_TEST_BUNDLE);
+
+  }
+
+  // Test formatters.
+  /**
+   *
+   */
+}
