From 8d3eaa1ccf1e7f7d20ecbb19a77e39c028a72706 Mon Sep 17 00:00:00 2001
From: Angie Byron <webchick@24967.no-reply.drupal.org>
Date: Mon, 12 Oct 2009 05:22:57 +0000
Subject: [PATCH] #560780 by quicksketch, ksenzee, Arancaytar, yched, and
 arianek: Added Image Field to image.module. Hellooooo, native image handling
 in core! :D

---
 CHANGELOG.txt                 |   4 +
 modules/field/field.info.inc  |  40 ++-
 modules/field/field.module    |   5 +-
 modules/file/file.field.inc   | 131 +++------
 modules/file/file.module      |  77 ++----
 modules/file/tests/file.test  |   4 +-
 modules/image/image-rtl.css   |  12 +
 modules/image/image.css       |  15 ++
 modules/image/image.field.inc | 490 ++++++++++++++++++++++++++++++++++
 modules/image/image.info      |   1 +
 modules/image/image.module    |  67 ++++-
 modules/image/image.test      |   1 +
 12 files changed, 686 insertions(+), 161 deletions(-)
 create mode 100644 modules/image/image-rtl.css
 create mode 100644 modules/image/image.css
 create mode 100644 modules/image/image.field.inc

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index b85768e5e5dd..607cce2bc19e 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -127,11 +127,15 @@ Drupal 7.0, xxxx-xx-xx (development version)
       hook_file_mimetype_mapping_alter().
     * Added the hook_file_url_alter() hook, which makes it possible to serve
       files from a CDN.
+    * Added a field specifically for uploading files, previously provided by
+      the contributed module FileField. 
 - Image handling:
     * Improved image handling, including better support for add-on image
       libraries.
     * Added API and interface for creating advanced image thumbnails.
     * Inclusion of additional effects such as rotate and desaturate.
+    * Added a field specifically for uploading images, previously provided by
+      the contributed module ImageField. 
 - Added aliased multi-site support:
     * Added support for mapping domain names to sites directories.
 - Added RDF support:
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 8b273da6a7fb..f229a1d3977e 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -507,14 +507,48 @@ function field_info_bundle_entity($bundle) {
 /**
  * Return array of all field data, keyed by field name.
  *
+ * @param $bundle_type
+ *   (optional) The bundle type on which to filter the list of fields. In the
+ *   case of nodes, this is the node type.
+ * @param $field
+ *   (optional) A field array or name on which to filter the list.
+ * @param $field_type
+ *   (optional) A field type on which to filter the list.
  * @return
  *   An array of Field objects. Each Field object has an additional
  *   property, bundles, which is an array of all the bundles to which
  *   this field belongs.
  */
-function field_info_fields() {
-  $info = _field_info_collate_fields();
-  return $info['fields'];
+function field_info_fields($bundle_type = NULL, $field = NULL, $field_type = NULL) {
+  // Build the list of fields to be used for retrieval.
+  if (isset($field)) {
+    if (is_string($field)) {
+      $field = field_info_field($field);
+    }
+    $fields = array($field['field_name'] => $field);
+  }
+  elseif (isset($bundle_type)) {
+    $instances = field_info_instances($bundle_type);
+    $fields = array();
+    foreach ($instances as $field_name => $instance) {
+      $fields[$field_name] = field_info_field($field_name);
+    }
+  }
+  else {
+    $info = _field_info_collate_fields();
+    $fields = $info['fields'];
+  }
+
+  // If a field type was given, filter the list down to fields of that type.
+  if (isset($field_type)) {
+    foreach ($fields as $key => $field) {
+      if ($field['type'] != $field_type) {
+        unset($fields[$key]);
+      }
+    }
+  }
+
+  return $fields;
 }
 
 /**
diff --git a/modules/field/field.module b/modules/field/field.module
index 080091b683ef..0a14e084db3d 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -170,9 +170,12 @@ function field_theme() {
   );
   $field_formatters = field_info_formatter_types(NULL);
   foreach ($field_formatters as $key => $field_formatter) {
-	$items["field_formatter_$key"] = array(
+    $items['field_formatter_' . $key] = array(
       'arguments' => array('element' => NULL),
     );
+    if (isset($field_formatter['theme'])) {
+      $items['field_formatter_' . $key] += $field_formatter['theme'];
+    }
   }
   return $items;
 }
diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc
index 1687e834ee5c..d08135817886 100644
--- a/modules/file/file.field.inc
+++ b/modules/file/file.field.inc
@@ -18,12 +18,12 @@ function file_field_info() {
         'display_field' => 0,
         'display_default' => 0,
         'uri_scheme' => 'public',
-        'default_file' => 0,
       ),
       'instance_settings' => array(
         'file_extensions' => 'txt',
         'file_directory' => '',
         'max_filesize' => '',
+        'description_field' => 0,
       ),
       'default_widget' => 'file_file',
       'default_formatter' => 'file_default',
@@ -51,11 +51,10 @@ function file_field_schema($field) {
         'not null' => TRUE,
         'default' => 1,
       ),
-      'data' => array(
-        'description' => 'Serialized additional data about the file, such as a description.',
+      'description' => array(
+        'description' => 'A description of the file.',
         'type' => 'text',
         'not null' => FALSE,
-        'serialize' => TRUE,
       ),
     ),
     'indexes' => array(
@@ -101,14 +100,6 @@ function file_field_settings_form($field, $instance, $has_data) {
     '#disabled' => $has_data,
   );
 
-  $form['default_file'] = array(
-    '#title' => t('Default file'),
-    '#type' => 'managed_file',
-    '#description' => t('If no file is uploaded, this file will be used on display.'),
-    '#default_value' => $field['settings']['default_file'],
-    '#upload_location' => 'public://default_files/',
-  );
-
   return $form;
 }
 
@@ -118,15 +109,12 @@ function file_field_settings_form($field, $instance, $has_data) {
 function file_field_instance_settings_form($field, $instance) {
   $settings = $instance['settings'];
 
-  $form['#attached']['js'][] = drupal_get_path('module', 'file') . '/file.js';
-
-  $form['max_filesize'] = array(
+  $form['file_directory'] = array(
     '#type' => 'textfield',
-    '#title' => t('Maximum upload size'),
-    '#default_value' => $settings['max_filesize'],
-    '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
-    '#size' => 10,
-    '#element_validate' => array('_file_generic_settings_max_filesize'),
+    '#title' => t('File directory'),
+    '#default_value' => $settings['file_directory'],
+    '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.', array('%directory' => variable_get('file_directory_path', 'files') . '/')),
+    '#element_validate' => array('_file_generic_settings_file_directory_validate'),
     '#weight' => 3,
   );
 
@@ -136,26 +124,28 @@ function file_field_instance_settings_form($field, $instance) {
     '#type' => 'textfield',
     '#title' => t('Allowed file extensions'),
     '#default_value' => $extensions,
-    '#size' => 64,
     '#description' => t('Separate extensions with a space or comma and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'),
     '#element_validate' => array('_file_generic_settings_extensions'),
-    '#weight' => 4,
+    '#weight' => 1,
   );
 
-  $form['destination'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Upload destination'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
+  $form['max_filesize'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Maximum upload size'),
+    '#default_value' => $settings['max_filesize'],
+    '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
+    '#size' => 10,
+    '#element_validate' => array('_file_generic_settings_max_filesize'),
     '#weight' => 5,
   );
-  $form['destination']['file_directory'] = array(
-    '#type' => 'textfield',
-    '#title' => t('File directory'),
-    '#default_value' => $settings['file_directory'],
-    '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.', array('%directory' => variable_get('file_directory_path', 'files') . '/')),
-    '#element_validate' => array('_file_generic_settings_file_directory_validate'),
-    '#parents' => array('instance', 'settings', 'file_directory'),
+
+  $form['description_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable <em>Description</em> field'),
+    '#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '',
+    '#description' => t('The description field allows users to enter a description about the uploaded file.'),
+    '#parents' => array('instance', 'settings', 'description_field'),
+    '#weight' => 11,
   );
 
   return $form;
@@ -230,9 +220,7 @@ function file_field_load($obj_type, $objects, $field, $instances, $langcode, &$i
       if (empty($item['fid']) || !isset($files[$item['fid']])) {
         $items[$obj_id][$delta] = NULL;
       }
-      // Unserialize the data column.
       else {
-        $item['data'] = unserialize($item['data']);
         $items[$obj_id][$delta] = array_merge($item, (array) $files[$item['fid']]);
       }
     }
@@ -243,19 +231,10 @@ function file_field_load($obj_type, $objects, $field, $instances, $langcode, &$i
  * Implement hook_field_sanitize().
  */
 function file_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) {
-  // If there are no files specified at all, use the default.
-  if (empty($items) && $field['settings']['default_file']) {
-    if ($file = file_load($field['settings']['default_file'])) {
-      $items[0] = (array) $file;
-      $items[0]['is_default'] = TRUE;
-    }
-  }
   // Remove files from being displayed if they're not displayed.
-  else {
-    foreach ($items as $delta => $item) {
-      if (!file_field_displayed($item, $field)) {
-        unset($items[$delta]);
-      }
+  foreach ($items as $delta => $item) {
+    if (!file_field_displayed($item, $field)) {
+      unset($items[$delta]);
     }
   }
 
@@ -274,11 +253,6 @@ function file_field_insert($obj_type, $object, $field, $instance, $langcode, &$i
  * Implement hook_field_update().
  */
 function file_field_update($obj_type, $object, $field, $instance, $langcode, &$items) {
-  // Serialize the data column before storing.
-  foreach ($items as $delta => $item) {
-    $items[$delta]['data'] = serialize($item['data']);
-  }
-
   // Check for files that have been removed from the object.
 
   // On new revisions, old files are always maintained in the previous revision.
@@ -416,11 +390,10 @@ function file_field_formatter_info() {
 function file_field_widget_info() {
   return array(
     'file_generic' => array(
-      'label' => t('Generic file'),
+      'label' => t('File'),
       'field types' => array('file'),
       'settings' => array(
         'progress_indicator' => 'throbber',
-        'description_field' => 0,
       ),
       'behaviors' => array(
         'multiple values' => FIELD_BEHAVIOR_CUSTOM,
@@ -446,26 +419,10 @@ function file_field_widget_settings_form($field, $instance) {
     ),
     '#default_value' => $settings['progress_indicator'],
     '#description' => t('The throbber display does not show the status of uploads but takes up space. The progress bar is helpful for monitoring progress on large uploads.'),
-    '#weight' => 2,
+    '#weight' => 16,
     '#access' => file_progress_implementation(),
   );
 
-  $form['additional'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Additional fields'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#weight' => 10,
-  );
-
-  $form['additional']['description_field'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Enable <em>Description</em> field'),
-    '#default_value' => $settings['description_field'],
-    '#description' => t('The description field allows users to enter a description about the uploaded file.'),
-    '#parents' => array('instance', 'widget', 'settings', 'description_field'),
-  );
-
   return $form;
 }
 
@@ -477,8 +434,8 @@ function file_field_widget(&$form, &$form_state, $field, $instance, $langcode, $
 
   $defaults = array(
     'fid' => 0,
-    'display' => $field['settings']['display_default'],
-    'data' => array('description' => ''),
+    'display' => !empty($field['settings']['display_default']),
+    'description' => '',
   );
 
   // Retrieve any values set in $form_state, as will be the case during AJAX
@@ -632,7 +589,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) {
   $return += array(
     'fid' => 0,
     'display' => 1,
-    'data' => array(),
+    'description' => '',
   );
 
   return $return;
@@ -653,17 +610,8 @@ function file_field_widget_process($element, &$form_state, $form) {
 
   $element['#theme'] = 'file_widget';
 
-  // Data placeholder for widgets to store additional data.
-  $element['data'] = array(
-    '#tree' => TRUE,
-    '#title' => t('File data'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#access' => (bool) $item['fid'],
-  );
-
   // Add the display field if enabled.
-  if ($field['settings']['display_field'] && $item['fid']) {
+  if (!empty($field['settings']['display_field']) && $item['fid']) {
     $element['display'] = array(
       '#type' => empty($item['fid']) ? 'hidden' : 'checkbox',
       '#title' => t('Include file in display'),
@@ -679,11 +627,11 @@ function file_field_widget_process($element, &$form_state, $form) {
   }
 
   // Add the description field if enabled.
-  if ($settings['description_field'] && $item['fid']) {
-    $element['data']['description'] = array(
+  if (!empty($instance['settings']['description_field']) && $item['fid']) {
+    $element['description'] = array(
       '#type' => 'textfield',
       '#title' => t('Description'),
-      '#value' => isset($item['data']['description']) ? $item['data']['description'] : '',
+      '#value' => isset($item['description']) ? $item['description'] : '',
       '#type' => variable_get('file_description_type', 'textfield'),
       '#maxlength' => variable_get('file_description_length', 128),
       '#description' => t('The description may be used as the label of the link to the file.'),
@@ -769,6 +717,7 @@ function theme_file_widget_multiple($variables) {
   $element = $variables['element'];
 
   $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
 
   // Get our list of widgets in order.
   $widgets = array();
@@ -784,7 +733,7 @@ function theme_file_widget_multiple($variables) {
   // Build up a table of applicable fields.
   $headers = array();
   $headers[] = t('File information');
-  if ($field['settings']['display_field']) {
+  if (!empty($field['settings']['display_field'])) {
     $headers[] = array(
       'data' => t('Display'),
       'class' => array('checkbox'),
@@ -812,7 +761,7 @@ function theme_file_widget_multiple($variables) {
 
     // Render the "Display" option in its own own column.
     $display = '';
-    if ($field['settings']['display_field']) {
+    if (!empty($field['settings']['display_field'])) {
       unset($element[$key]['display']['#title']);
       $display = array(
         'data' => drupal_render($element[$key]['display']),
@@ -830,7 +779,7 @@ function theme_file_widget_multiple($variables) {
 
     $row = array();
     $row[] = $information;
-    if ($field['settings']['display_field']) {
+    if (!empty($field['settings']['display_field'])) {
       $row[] = $display;
     }
     $row[] = $weight;
diff --git a/modules/file/file.module b/modules/file/file.module
index a18460ec960d..9928b1887f46 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -87,8 +87,11 @@ function file_theme() {
 
 /**
  * Implement hook_file_download().
+ *
+ * This function takes an extra parameter $field_type so that it may
+ * be re-used by other File-like modules, such as Image.
  */
-function file_file_download($uri) {
+function file_file_download($uri, $field_type = 'file') {
   global $user;
 
   // Get the file record based on the URI. If not in the database just return.
@@ -101,11 +104,11 @@ function file_file_download($uri) {
   }
 
   // Find out which (if any) file fields contain this file.
-  $references = file_get_file_references($file);
+  $references = file_get_file_references($file, NULL, FIELD_LOAD_REVISION, $field_type);
 
   // TODO: Check field-level access if available here.
 
-  $denied = NULL;
+  $denied = $file->status ? NULL : FALSE;
   // Check access to content containing the file fields. If access is allowed
   // to any of this content, allow the download.
   foreach ($references as $field_name => $field_references) {
@@ -610,11 +613,11 @@ function theme_file_link($variables) {
   );
 
   // Use the description as the link text if available.
-  if (empty($file->data['description'])) {
+  if (empty($file->description)) {
     $link_text = check_plain($file->filename);
   }
   else {
-    $link_text = check_plain($file->data['description']);
+    $link_text = check_plain($file->description);
     $options['attributes']['title'] = check_plain($file->filename);
   }
 
@@ -838,62 +841,28 @@ function file_icon_map($file) {
  */
 
 /**
- * Return an array of file fields in an bundle or by field name.
- *
- * @param $bundle_type
- *   (optional) The bundle type on which to filter the list of fields. In the
- *   case of nodes, this is the node type.
- * @param $field
- *   (optional) A field array or name on which to filter the list.
- */
-function file_get_field_list($bundle_type = NULL, $field = NULL) {
-  // Build the list of fields to be used for retrieval.
-  if (isset($field)) {
-    if (is_string($field)) {
-      $field = field_info_field($field);
-    }
-    $fields = array($field['field_name'] => $field);
-  }
-  elseif (isset($bundle_type)) {
-    $instances = field_info_instances($bundle_type);
-    $fields = array();
-    foreach ($instances as $field_name => $instance) {
-      $fields[$field_name] = field_info_field($field_name);
-    }
-  }
-  else {
-    $fields = field_info_fields();
-  }
-
-  // Filter down the list to just file fields.
-  foreach ($fields as $key => $field) {
-    if ($field['type'] != 'file') {
-      unset($fields[$key]);
-    }
-  }
-
-  return $fields;
-}
-
-/**
- * Count the number of times the file is referenced within a field.
+ * Count the number of times the file is referenced.
  *
  * @param $file
  *   A file object.
  * @param $field
- *   Optional. The CCK field array or field name as a string.
+ *   (optional) A CCK field array or field name as a string. If provided,
+ *   limits the reference check to the given field.
+ * @param $field_type
+ *   (optional) The name of a field type. If provided, limits the reference
+ *   check to fields of the given type.
  * @return
  *   An integer value.
  */
-function file_get_file_reference_count($file, $field = NULL) {
-  $fields = file_get_field_list(NULL, $field);
+function file_get_file_reference_count($file, $field = NULL, $field_type = 'file') {
+  $fields = field_info_fields(NULL, $field, $field_type);
   $types = field_info_fieldable_types();
   $reference_count = 0;
 
   foreach ($fields as $field) {
     // TODO: Use a more efficient mechanism rather than actually retrieving
     // all the references themselves, such as using a COUNT() query.
-    $references = file_get_file_references($file, $field);
+    $references = file_get_file_references($file, $field, FIELD_LOAD_REVISION, $field_type);
     foreach ($references as $obj_type => $type_references) {
       $reference_count += count($type_references);
     }
@@ -925,22 +894,26 @@ function file_get_file_reference_count($file, $field = NULL) {
 
 
 /**
- * Get a list of references to a file by bundle and ID.
+ * Get a list of references to a file.
  *
  * @param $file
  *   A file object.
  * @param $field
- *   (optional) A field array to be used for this check.
+ *   (optional) A field array to be used for this check. If given, limits the
+ *   reference check to the given field.
  * @param $age
  *   (optional) A constant that specifies which references to count. Use
  *   FIELD_LOAD_REVISION to retrieve all references within all revisions or
  *   FIELD_LOAD_CURRENT to retrieve references only in the current revisions.
+ * @param $field_type
+ *   Optional. The name of a field type. If given, limits the reference check to
+ *   fields of the given type.
  * @return
  *   An integer value.
  */
-function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION) {
+function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
   $references = drupal_static(__FUNCTION__, array());
-  $fields = file_get_field_list(NULL, $field);
+  $fields = field_info_fields(NULL, $field, $field_type);
 
   foreach ($fields as $field_name => $file_field) {
     if (!isset($references[$field_name])) {
diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test
index 2a39b8b19b3f..ed89fc5d5916 100644
--- a/modules/file/tests/file.test
+++ b/modules/file/tests/file.test
@@ -288,10 +288,10 @@ class FileFieldDisplayTestCase extends FileFieldTestCase {
       'display_field' => '1',
       'display_default' => '1',
     );
-    $instance_settings = array();
-    $widget_settings = array(
+    $instance_settings = array(
       'description_field' => '1',
     );
+    $widget_settings = array();
     $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
     $field = field_info_field($field_name);
     $instance = field_info_instance($field_name, $type_name);
diff --git a/modules/image/image-rtl.css b/modules/image/image-rtl.css
new file mode 100644
index 000000000000..59c46605d866
--- /dev/null
+++ b/modules/image/image-rtl.css
@@ -0,0 +1,12 @@
+/* $Id$ */
+
+/**
+ * Image upload widget.
+ */
+div.image-preview {
+  float: right; /* RTL */
+  padding: 0 0 10px 10px; /* RTL */
+}
+div.image-widget-data {
+  float: right; /* RTL */
+}
diff --git a/modules/image/image.css b/modules/image/image.css
new file mode 100644
index 000000000000..3f4cc5cd34b7
--- /dev/null
+++ b/modules/image/image.css
@@ -0,0 +1,15 @@
+/* $Id$ */
+
+/**
+ * Image upload widget.
+ */
+div.image-preview {
+  float: left; /* RTL */
+  padding: 0 10px 10px 0; /* RTL */
+}
+div.image-widget-data {
+  float: left; /* RTL */
+}
+div.image-widget-data input.text-field {
+  width: auto;
+}
diff --git a/modules/image/image.field.inc b/modules/image/image.field.inc
new file mode 100644
index 000000000000..963187fdc2bb
--- /dev/null
+++ b/modules/image/image.field.inc
@@ -0,0 +1,490 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Implement an image field, based on the file module's file field.
+ */
+
+/**
+ * Implement hook_field_info().
+ */
+function image_field_info() {
+  return array(
+    'image' => array(
+      'label' => t('Image'),
+      'description' => t('This field stores the ID of an image file as an integer value.'),
+      'settings' => array(
+        'uri_scheme' => 'public',
+        'default_image' => 0,
+      ),
+      'instance_settings' => array(
+        'file_extensions' => 'png gif jpg jpeg',
+        'file_directory' => '',
+        'max_filesize' => '',
+        'alt_field' => 0,
+        'title_field' => 0,
+        'max_resolution' => '',
+        'min_resolution' => '',
+      ),
+      'default_widget' => 'image_image',
+      'default_formatter' => 'image',
+    ),
+  );
+}
+
+/**
+ * Implement hook_field_schema().
+ */
+function image_field_schema($field) {
+  return array(
+    'columns' => array(
+      'fid' => array(
+        'description' => 'The {files}.fid being referenced in this field.',
+        'type' => 'int',
+        'not null' => FALSE,
+        'unsigned' => TRUE,
+      ),
+      'alt' => array(
+        'description' => "Alternative image text, for the image's 'alt' attribute.",
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+      'title' => array(
+        'description' => "Image title text, for the image's 'title' attribute.",
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+    ),
+    'indexes' => array(
+      'fid' => array('fid'),
+    ),
+  );
+}
+
+/**
+ * Implement hook_field_settings_form().
+ */
+function image_field_settings_form($field, $instance) {
+  $defaults = field_info_field_settings($field['type']);
+  $settings = array_merge($defaults, $field['settings']);
+
+  $scheme_options = array();
+  foreach (file_get_stream_wrappers() as $scheme => $stream_wrapper) {
+    if ($scheme != 'temporary') {
+      $scheme_options[$scheme] = $stream_wrapper['name'];
+    }
+  }
+  $form['uri_scheme'] = array(
+    '#type' => 'radios',
+    '#title' => t('Upload destination'),
+    '#options' => $scheme_options,
+    '#default_value' => $settings['uri_scheme'],
+    '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
+  );
+
+  $form['default_image'] = array(
+    '#title' => t('Default image'),
+    '#type' => 'managed_file',
+    '#description' => t('If no image is uploaded, this image will be shown on display.'),
+    '#default_value' => $field['settings']['default_image'],
+    '#upload_location' => 'public://default_images/',
+  );
+
+  return $form;
+
+}
+
+/**
+ * Implement hook_field_instance_settings_form().
+ */
+function image_field_instance_settings_form($field, $instance) {
+  $settings = $instance['settings'];
+
+  // Use the file field instance settings form as a basis.
+  $form = file_field_instance_settings_form($field, $instance);
+
+  // Add maximum and minimum resolution settings.
+  $max_resolution = explode('x', $settings['max_resolution']) + array('', '');
+  $form['max_resolution'] = array(
+    '#title' => t('Maximum image resolution'),
+    '#element_validate' => array('_image_field_resolution_validate'),
+    '#theme_wrappers' => array('form_element'),
+    '#weight' => 4.1,
+    '#description' => t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'),
+  );
+  $form['max_resolution']['x'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $max_resolution[0],
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#field_suffix' => ' x ',
+    '#theme_wrappers' => array(),
+  );
+  $form['max_resolution']['y'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $max_resolution[1],
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#field_suffix' => ' ' . t('pixels'),
+    '#theme_wrappers' => array(),
+  );
+
+  $min_resolution = explode('x', $settings['min_resolution']) + array('', '');
+  $form['min_resolution'] = array(
+    '#title' => t('Minimum image resolution'),
+    '#element_validate' => array('_image_field_resolution_validate'),
+    '#theme_wrappers' => array('form_element'),
+    '#weight' => 4.2,
+    '#description' => t('The minimum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
+  );
+  $form['min_resolution']['x'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $min_resolution[0],
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#field_suffix' => ' x ',
+    '#theme_wrappers' => array(),
+  );
+  $form['min_resolution']['y'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $min_resolution[1],
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#field_suffix' => ' ' . t('pixels'),
+    '#theme_wrappers' => array(),
+  );
+
+  // Remove the description option.
+  unset($form['description_field']);
+
+  // Add title and alt configuration options.
+  $form['alt_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable <em>Alt</em> field'),
+    '#default_value' => $settings['alt_field'],
+    '#description' => t('The alt attribute may be used by search engines, screen readers, and when the image cannot be loaded.'),
+    '#weight' => 10,
+  );
+  $form['title_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable <em>Title</em> field'),
+    '#default_value' => $settings['title_field'],
+    '#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image.'),
+    '#weight' => 11,
+  );
+
+  return $form;
+}
+
+/**
+ * Element validate function for resolution fields.
+ */
+function _image_field_resolution_validate($element, &$form_state) {
+  if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) {
+    foreach (array('x', 'y') as $dimension) {
+      $value = $element[$dimension]['#value'];
+      if (!is_numeric($value)) {
+        form_error($element[$dimension], t('Height and width values must be numeric.'));
+        return;
+      }
+      if (intval($value) == 0) {
+        form_error($element[$dimension], t('Both a height and width value must be specified in the !name field.', array('!name' => $element['#title'])));
+        return;
+      }
+    }
+    form_set_value($element, intval($element['x']['#value']) . 'x' . intval($element['y']['#value']), $form_state);
+  }
+  else {
+    form_set_value($element, '', $form_state);
+  }
+}
+
+/**
+ * Implement hook_field_load().
+ */
+function image_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
+  file_field_load($obj_type, $objects, $field, $instances, $langcode, $items, $age);
+}
+
+/**
+ * Implement hook_field_sanitize().
+ */
+function image_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) {
+  // If there are no files specified at all, use the default.
+  if (empty($items) && $field['settings']['default_image']) {
+    if ($file = file_load($field['settings']['default_image'])) {
+      $items[0] = (array) $file + array(
+        'is_default' => TRUE,
+        'alt' => '',
+        'title' => '',
+      );
+    }
+  }
+}
+
+/**
+ * Implement hook_field_insert().
+ */
+function image_field_insert($obj_type, $object, $field, $instance, $langcode, &$items) {
+  image_field_update($obj_type, $object, $field, $instance, $langcode, $items);
+}
+
+/**
+ * Implement hook_field_update().
+ */
+function image_field_update($obj_type, $object, $field, $instance, $langcode, &$items) {
+  file_field_update($obj_type, $object, $field, $instance, $langcode, $items);
+}
+
+/**
+ * Implement hook_field_delete().
+ */
+function image_field_delete($obj_type, $object, $field, $instance, $langcode, &$items) {
+  file_field_delete($obj_type, $object, $field, $instance, $langcode, $items);
+}
+
+/**
+ * Implement hook_field_delete_revision().
+ */
+function image_field_delete_revision($obj_type, $object, $field, $instance, $langcode, &$items) {
+  file_field_delete_revision($obj_type, $object, $field, $instance, $langcode, $items);
+}
+
+/**
+ * Implement hook_field_is_empty().
+ */
+function image_field_is_empty($item, $field) {
+  return file_field_is_empty($item, $field);
+}
+
+/**
+ * Implement hook_field_widget_info().
+ */
+function image_field_widget_info() {
+  return array(
+    'image_image' => array(
+      'label' => t('Image'),
+      'field types' => array('image'),
+      'settings' => array(
+        'progress_indicator' => 'throbber',
+        'preview_image_style' => 'thumbnail',
+      ),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+        'default value' => FIELD_BEHAVIOR_NONE,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implement hook_field_widget_settings_form().
+ */
+function image_field_widget_settings_form($field, $instance) {
+  $widget = $instance['widget'];
+  $settings = $widget['settings'];
+
+  // Use the file widget settings form.
+  $form = file_field_widget_settings_form($field, $instance);
+
+  $form['preview_image_style'] = array(
+    '#title' => t('Preview image style'),
+    '#type' => 'select',
+    '#options' => array('' => '<' . t('no preview') . '>') + image_style_options(FALSE),
+    '#default_value' => $settings['preview_image_style'],
+    '#description' => t('The preview image will be shown while editing the content.'),
+    '#weight' => 15,
+  );
+
+  return $form;
+}
+
+/**
+ * Implementation of hook_field_widget().
+ */
+function image_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) {
+  $elements = file_field_widget($form, $form_state, $field, $instance, $items, $delta);
+  $settings = $instance['settings'];
+
+  foreach (element_children($elements) as $delta) {
+    // Add upload resolution validation.
+    if ($settings['max_resolution'] || $settings['min_resolution']) {
+      $elements[$delta]['#upload_validators']['file_validate_image_resolution'] = array($settings['max_resolution'], $settings['min_resolution']);
+    }
+
+    // If not using custom extension validation, ensure this is an image.
+    $supported_extensions = array('png', 'gif', 'jpg', 'jpeg');
+    $extensions = isset($elements[$delta]['#upload_validators']['file_validate_extensions'][0]) ? $elements[$delta]['#upload_validators']['file_validate_extensions'][0] : implode(' ', $supported_extensions);
+    $extensions = array_intersect(explode(' ', $extensions), $supported_extensions);
+    $elements[$delta]['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions);
+
+    // Add all extra functionality provided by the image widget.
+    $elements[$delta]['#process'][] = 'image_field_widget_process';
+  }
+
+  if ($field['cardinality'] == 1) {
+    // If there's only one field, return it as delta 0.
+    if (empty($elements[0]['#default_value']['fid'])) {
+      $elements[0]['#description'] = theme('file_upload_help', array('description' => $instance['description'], 'upload_validators' => $elements[0]['#upload_validators']));
+    }
+  }
+  else {
+    $elements['#file_upload_description'] = theme('file_upload_help', array('upload_validators' => $elements[0]['#upload_validators']));
+  }
+  return $elements;
+}
+
+/**
+ * An element #process callback for the image_image field type.
+ *
+ * Expands the image_image type to include the alt and title fields.
+ */
+function image_field_widget_process($element, &$form_state, $form) {
+  $item = $element['#value'];
+  $item['fid'] = $element['fid']['#value'];
+
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $settings = $instance['settings'];
+  $widget_settings = $instance['widget']['settings'];
+
+  $element['#theme'] = 'image_widget';
+  $element['#attached']['css'][] = drupal_get_path('module', 'image') . '/image.css';
+
+  // Add the image preview.
+  if ($element['#file'] && $widget_settings['preview_image_style']) {
+    $element['preview'] = array(
+      '#type' => 'markup',
+      '#markup' => theme('image_style', array('style_name' => $widget_settings['preview_image_style'], 'path' => $element['#file']->uri, 'getsize' => FALSE)),
+    );
+  }
+
+  // Add the additional alt and title fields.
+  $element['alt'] = array(
+    '#title' => t('Alternate text'),
+    '#type' => 'textfield',
+    '#default_value' => isset($item['alt']) ? $item['alt'] : '',
+    '#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
+    '#maxlength' => variable_get('image_alt_length', 80), // See http://www.gawds.org/show.php?contentid=28.
+    '#weight' => -2,
+    '#access' => (bool) $item['fid'] && $settings['alt_field'],
+  );
+  $element['title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => isset($item['title']) ? $item['title'] : '',
+    '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
+    '#maxlength' => variable_get('image_title_length', 500),
+    '#weight' => -1,
+    '#access' => (bool) $item['fid'] && $settings['title_field'],
+  );
+
+  return $element;
+}
+
+/**
+ * Theme the display of the image field widget.
+ */
+function theme_image_widget($variables) {
+  $element = $variables['element'];
+  $output = '';
+  $output .= '<div class="image-widget form-managed-file clearfix">';
+
+  if (isset($element['preview'])) {
+    $output .= '<div class="image-preview">';
+    $output .= drupal_render($element['preview']);
+    $output .= '</div>';
+  }
+
+  $output .= '<div class="image-widget-data">';
+  if ($element['fid']['#value'] != 0) {
+    $element['filename']['#markup'] .= ' <span class="file-size">(' . format_size($element['#file']->filesize) . ')</span> ';
+  }
+  $output .= drupal_render_children($element);
+  $output .= '</div>';
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Implement hook_field_formatter_info().
+ */
+function image_field_formatter_info() {
+  $formatters = array(
+    'image' => array(
+      'label' => t('Image'),
+      'field types' => array('image'),
+    ),
+    'image_link_content' => array(
+      'label' => t('Image linked to content'),
+      'field types' => array('image'),
+    ),
+    'image_link_file' => array(
+      'label' => t('Image linked to file'),
+      'field types' => array('image'),
+    ),
+  );
+
+  foreach (image_styles() as $style) {
+    $formatters['image__' . $style['name']] = array(
+      'label' => t('Image "@style"', array('@style' => $style['name'])),
+      'field types' => array('image'),
+      'theme' => array('function' => 'theme_field_formatter_image'),
+    );
+    $formatters['image_link_content__' . $style['name']] = array(
+      'label' => t('Image "@style" linked to content', array('@style' => $style['name'])),
+      'field types' => array('image'),
+      'theme' => array('function' => 'theme_field_formatter_image_link_content'),
+    );
+    $formatters['image_link_file__' . $style['name']] = array(
+      'label' => t('Image "@style" linked to file', array('@style' => $style['name'])),
+      'field types' => array('image'),
+      'theme' => array('function' => 'theme_field_formatter_image_link_file'),
+    );
+  }
+
+  return $formatters;
+}
+
+/**
+ * Theme function for 'image' image field formatter.
+ */
+function theme_field_formatter_image($variables) {
+  $element = $variables['element'];
+  $image = array(
+    'path' => $element['#item']['uri'],
+    'alt' => $element['#item']['alt'],
+    'title' => $element['#item']['title'],
+  );
+
+  // Check if this requires a particular image style.
+  $matches = array();
+  if (preg_match('/__([a-z0-9_]+)/', $element['#formatter'], $matches)) {
+    $image['style_name'] = $matches[1];
+    return theme('image_style', $image);
+  }
+  else {
+    return theme('image', $image);
+  }
+}
+
+/**
+ * Theme function for 'image_link_content' image field formatter.
+ */
+function theme_field_formatter_image_link_content($variables) {
+  $element = $variables['element'];
+  list($id, $vid, $bundle) = field_extract_ids($element['#object_type'], $element['#object']);
+  return l(theme('field_formatter_image', $variables), $element['#object_type'] . '/' . $id, array('html' => TRUE));
+}
+
+/**
+ * Theme function for 'image_link_file' image field formatter.
+ */
+function theme_field_formatter_image_link_file($variables) {
+  $element = $variables['element'];
+  return l(theme('field_formatter_image', $variables), file_create_url($element['#item']['uri']), array('html' => TRUE));
+}
diff --git a/modules/image/image.info b/modules/image/image.info
index 0e51ce447b19..e57d62cee38c 100644
--- a/modules/image/image.info
+++ b/modules/image/image.info
@@ -7,5 +7,6 @@ core = 7.x
 files[] = image.module
 files[] = image.admin.inc
 files[] = image.effects.inc
+files[] = image.field.inc
 files[] = image.install
 files[] = image.test
diff --git a/modules/image/image.module b/modules/image/image.module
index bb28304071e5..f745662d380b 100644
--- a/modules/image/image.module
+++ b/modules/image/image.module
@@ -6,6 +6,9 @@
  * Exposes global functionality for creating image styles.
  */
 
+// Load all Field module hooks for Image.
+require_once DRUPAL_ROOT . '/modules/image/image.field.inc';
+
 /**
  * Implement of hook_help().
  */
@@ -126,6 +129,7 @@ function image_menu() {
  */
 function image_theme() {
   return array(
+    // Theme functions in image.module.
     'image_style' => array(
       'arguments' => array(
         'style_name' => NULL,
@@ -136,6 +140,8 @@ function image_theme() {
         'getsize' => TRUE,
       ),
     ),
+
+    // Theme functions in image.admin.inc.
     'image_style_list' => array(
       'arguments' => array('styles' => NULL),
     ),
@@ -160,6 +166,20 @@ function image_theme() {
     'image_rotate_summary' => array(
       'arguments' => array('data' => NULL),
     ),
+
+    // Theme functions in image.field.inc.
+    'image_widget' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_image' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_image_link_content' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_image_link_file' => array(
+      'arguments' => array('element' => NULL),
+    ),
   );
 }
 
@@ -187,9 +207,12 @@ function image_flush_caches() {
  *
  * Control the access to files underneath the styles directory.
  */
-function image_file_download($filepath) {
-  if (strpos($filepath, 'styles/') === 0) {
-    $args = explode('/', $filepath);
+function image_file_download($uri) {
+  $path = file_uri_target($uri);
+
+  // Private file access for image style derivatives.
+  if (strpos($path, 'styles/') === 0) {
+    $args = explode('/', $path);
     // Discard the first part of the path (styles).
     array_shift($args);
     // Get the style name from the second part.
@@ -198,7 +221,7 @@ function image_file_download($filepath) {
     $original_path = implode('/', $args);
 
     // Check that the file exists and is an image.
-    if ($info = image_get_info($filepath)) {
+    if ($info = image_get_info($uri)) {
       // Check the permissions of the original to grant access to this image.
       $headers = module_invoke_all('file_download', $original_path);
       if (!in_array(-1, $headers)) {
@@ -217,6 +240,17 @@ function image_file_download($filepath) {
     }
     return -1;
   }
+
+  // Private file access for the original files. Note that we only
+  // check access for non-temporary images, since file.module will
+  // grant access for all temporary files.
+  $files = file_load_multiple(array(), array('uri' => $uri));
+  if (count($files)) {
+    $file = reset($files);
+    if ($file->status) {
+      return file_file_download($uri, 'image');
+    }
+  }
 }
 
 /**
@@ -235,6 +269,14 @@ function image_file_delete($file) {
   image_path_flush($file->uri);
 }
 
+/**
+ * Implement hook_file_references().
+ */
+function image_file_references($file) {
+  $count = file_get_file_reference_count($file, NULL, 'image');
+  return $count ? array('image' => $count) : NULL;
+}
+
 /**
  * Clear cached versions of a specific file in all styles.
  *
@@ -464,7 +506,7 @@ function image_style_generate() {
   // acquiring the lock.
   $success = file_exists($destination) || image_style_create_derivative($style, $path, $destination);
 
-  if ($lock_acquired) {
+  if (!empty($lock_acquired)) {
     lock_release($lock_name);
   }
 
@@ -600,17 +642,16 @@ function image_style_url($style_name, $path) {
  *
  * @param $style_name
  *   The name of the style to be used with this image.
- * @param $path
- *   The path to the image.
+ * @param $uri
+ *   The URI or path to the image.
  * @return
  *   The path to an image style image relative to Drupal's root.
  * @see image_style_url()
  */
-function image_style_path($style_name, $path) {
-  if ($target = file_uri_target($path)) {
-    $path = $target;
-  }
-  return variable_get('file_default_scheme', 'public') . '://styles/' . $style_name . '/' . $path;
+function image_style_path($style_name, $uri) {
+  $path = ($path = file_uri_target($uri)) ? $path : $uri;
+  $scheme = ($scheme = file_uri_scheme($uri)) ? $scheme : variable_get('file_default_scheme', 'public');
+  return $scheme . '://styles/' . $style_name . '/' . $path;
 }
 
 /**
@@ -773,6 +814,7 @@ function image_effect_delete($effect) {
  *   TRUE on success. FALSE if unable to perform the image effect on the image.
  */
 function image_effect_apply($image, $effect) {
+  module_load_include('inc', 'image', 'image.effects');
   if (function_exists($effect['effect callback'])) {
     return call_user_func($effect['effect callback'], $image, $effect['data']);
   }
@@ -811,6 +853,7 @@ function theme_image_style($variables) {
     $style_path = image_style_url($style_name, $path);
   }
   $variables['path'] = file_create_url($style_path);
+  $variables['getsize'] = FALSE;
   return theme('image', $variables);
 }
 
diff --git a/modules/image/image.test b/modules/image/image.test
index cbdcb1e08b6e..e50caf75b344 100644
--- a/modules/image/image.test
+++ b/modules/image/image.test
@@ -335,6 +335,7 @@ class ImageAdminStylesUnitTest extends DrupalWebTestCase {
     // Edit effect form.
 
     // Revisit each form to make sure the effect was saved.
+    drupal_static_reset('image_styles');
     $style = image_style_load($style_name);
 
     foreach ($style['effects'] as $ieid => $effect) {
-- 
GitLab