? test.patch
Index: image.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/Attic/image.install,v
retrieving revision 1.1.4.7
diff -u -r1.1.4.7 image.install
--- image.install	7 Jul 2007 20:29:53 -0000	1.1.4.7
+++ image.install	11 Jul 2007 18:34:52 -0000
@@ -4,9 +4,31 @@
 /**
  * Installing and updating image.module.
  */
-
 function image_install() {
-  //Nothing to do.
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      db_query("CREATE TABLE {image} (
+          `nid` INTEGER UNSIGNED NOT NULL,
+          `fid` INTEGER UNSIGNED NOT NULL,
+          `image_size` VARCHAR(255) NOT NULL,
+          PRIMARY KEY (`nid`, `image_size`),
+          INDEX image_fid(`fid`)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function image_uninstall() {
+  db_query('DROP TABLE {image}');
+
+  variable_del('image_default_path');
+  variable_del('image_max_upload_size');
+  variable_del('image_updated');
+  variable_del('image_sizes');
 }
 
 /**
@@ -107,3 +129,34 @@
 
   return $ret;
 }
+
+/**
+ * Move image files into their own table.
+ */
+function image_update_5() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("CREATE TABLE {image} (
+          `nid` INTEGER UNSIGNED NOT NULL,
+          `fid` INTEGER UNSIGNED NOT NULL,
+          `image_size` VARCHAR(255) NOT NULL,
+          PRIMARY KEY (`nid`, `image_size`),
+          INDEX image_fid(`fid`)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      
+      // Copy image files records into the new table.
+      $args = array_map('db_escape_string', array_keys(image_get_sizes()));
+      $cond = " IN ('". implode("', '",  $args) ."')";
+      $ret[] = update_sql("INSERT INTO {image} SELECT DISTINCT f.nid, f.fid, f.filename FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE n.type='image' AND f.filename". $cond);
+
+      // Remove old file_revision records.
+      $ret[] = update_sql("DELETE FROM {file_revisions} WHERE EXISTS (SELECT * FROM {image} WHERE {image}.fid = {file_revisions}.fid)");
+
+      break;
+  }
+
+  return $ret;
+}
Index: image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/Attic/image.module,v
retrieving revision 1.209.2.37
diff -u -r1.209.2.37 image.module
--- image.module	10 Jul 2007 23:08:33 -0000	1.209.2.37
+++ image.module	11 Jul 2007 18:41:37 -0000
@@ -120,7 +120,7 @@
     IMAGE_LINK_NEW => t('New window'),
   );
 
-  $sizes = _image_get_sizes();
+  $sizes = image_get_sizes();
   // Add some empty rows for user defined sizes.
   for ($i = count($sizes); $i < 6; $i++) {
     $sizes['new'. $i] = array(
@@ -282,9 +282,7 @@
   foreach ($nids as $nid) {
     if ($node = node_load($nid)) {
       if ($node->type == 'image') {
-        drupal_set_message(t("Rebuilding %node-title's resized images.", array('%node-title' => $node->title)));
-        _image_remove_derivatives($node);
-        _image_build_derivatives($node, FALSE);
+        $node->rebuild_images = TRUE;
         node_save($node);
       }
     }
@@ -294,10 +292,23 @@
 /**
  * Implementation of hook_prepare().
  */
-function image_prepare(&$node, $field_name) {
+function image_prepare(&$node, $field_name = NULL) {
+  // We need to be aware that a user may try to edit multiple image nodes at
+  // once. By using the $nid variable each node's files can be stored separately
+  // in the session.
+  $nid = ($node->nid) ? $node->nid : 'new_node';
+  // When you enter the edit view the first time we need to clear our files in
+  // session for this node. This is so if you upload a file, then decide you
+  // don't want it and reload the form (without posting), the files will be
+  // discarded.
+  if(count($_POST) == 0) {
+    unset($_SESSION['image_new_files'][$nid]);
+  }
+
   if (is_null($field_name)) {
     $field_name = 'image';
   }
+
   if ($file = file_check_upload($field_name)) {
     // Ensure the file is an image.
     $image_info = image_get_info($file->filepath);
@@ -314,7 +325,7 @@
 
     // Resize the original.
     $aspect_ratio = $image_info['height'] / $image_info['width'];
-    $original_size = _image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
+    $original_size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
     if (!empty($original_size['width']) && !empty($original_size['height'])) {
       $result = image_scale($file->filepath, $file->filepath, $original_size['width'], $original_size['height']);
       if ($result) {
@@ -333,11 +344,19 @@
 
     // We're good to go.
     $node->images[IMAGE_ORIGINAL] = $file->filepath;
+    $node->rebuild_images = FALSE;
     $node->new_file = TRUE;
 
     // Call hook to allow other modules to modify the original image.
     module_invoke_all('image_alter', $node, $file->filepath, IMAGE_ORIGINAL);
     _image_build_derivatives($node, TRUE);
+
+    // Store the new file into the session.
+    $_SESSION['image_new_files'][$nid] = $node->images;
+  }
+  // Reload new files uploaded in a previous preview.
+  else if (isset($_SESSION['image_new_files'][$nid])) {
+    $node->images = $_SESSION['image_new_files'][$nid];
   }
 }
 
@@ -360,7 +379,7 @@
 
   if ($type == 'node' && $node->type == 'image' && !$main) {
     $request = ($_GET['size']) ? $_GET['size'] : IMAGE_PREVIEW;
-    foreach (_image_get_sizes() as $key => $size) {
+    foreach (image_get_sizes() as $key => $size) {
       if ($size['link']) {
         // For smaller images some derivative images may not have been created.
         // The thumbnail and preview images will be equal to the original images
@@ -422,15 +441,10 @@
   }
 }
 
-function image_form_add_thumbnail($form_id, $edit) {
-  if ($edit['images']['thumbnail']) {
-    $node = (object)($edit);
-    $form = array(
-      '#type' => 'item',
-      '#title' => t('Thumbnail'),
-      '#value' => image_display($node, IMAGE_THUMBNAIL),
-      '#weight' => -10,
-    );
+function image_form_add_thumbnail($form, $form_values) {
+  if ($form_values['images']['thumbnail']) {
+    $node = (object)($form_values);
+    $form['#value'] = image_display($node, IMAGE_THUMBNAIL);
   }
   return $form;
 }
@@ -452,27 +466,28 @@
     '#default_value' => $node->title
   );
 
-  if ($node->new_file) {
-    $form['new_file'] = array('#type' => 'value', '#value' => TRUE);
-  }
-
   $form['images']['#tree'] = TRUE;
-  foreach (_image_get_sizes() as $key => $size) {
-    if ($node->new_file) {
-      $form['images'][$key] = array(
-        '#type' => 'hidden',
-        '#value' => $node->images[$key]
-      );
-    }
-    else {
-      $form['images'][$key] = array(
-        '#type' => 'hidden',
-        '#default_value' => $node->images[$key]
-      );
-    }
+  foreach (image_get_sizes() as $key => $size) {
+    $form['images'][$key] = array(
+      '#type' => 'value',
+      '#value' => $node->images[$key]
+    );
   }
 
-  $form['thumbnail']['#after_build'][] = 'image_form_add_thumbnail';
+  $form['thumbnail'] = array(
+    '#type' => 'item',
+    '#title' => t('Thumbnail'),
+    '#value' => image_display($node, IMAGE_THUMBNAIL),
+    '#weight' => -10,
+    '#after_build' => array('image_form_add_thumbnail'),
+  );
+
+  $form['rebuild_images'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Rebuild derivative images.'),
+    '#default_value' => FALSE,
+    '#description' => t('Check this to rebuild the derivative images for this node.'),
+  );
 
   $form['#attributes'] = array("enctype" => "multipart/form-data");
   $form['image'] = array(
@@ -496,15 +511,19 @@
 }
 
 function image_validate($node) {
-  if (empty($node->images[IMAGE_ORIGINAL])) {
+  $nid = ($node->nid) ? $node->nid : 'new_node';
+  if (!isset($node->images[IMAGE_ORIGINAL]) && !isset($_SESSION['image_new_files'][$nid])) {
     form_set_error('image', t('You must upload an image.'));
   }
 }
 
-function image_submit(&$node) {
-  if ($node->new_file) {
-    _image_remove_derivatives($node);
-    _image_build_derivatives($node, FALSE);
+function image_submit($node) {
+  $nid = ($node->nid) ? $node->nid : 'new_node';
+  if (isset($_SESSION['image_new_files'][$nid])) {
+    $node->new_file = TRUE;
+    $node->rebuild_images = FALSE;
+    $node->images = $_SESSION['image_new_files'][$nid];
+    unset($_SESSION['image_new_files'][$nid]);
   }
 }
 
@@ -512,7 +531,7 @@
  * Implementation of hook_view
  */
 function image_view($node, $teaser = 0, $page = 0) {
-  $sizes = _image_get_sizes();
+  $sizes = image_get_sizes();
   $size = IMAGE_PREVIEW;
   if (isset($_GET['size'])) {
     // Invalid size specified.
@@ -538,10 +557,10 @@
  * Implementation of hook_load
  */
 function image_load(&$node) {
-  $result = db_query("SELECT filename, filepath FROM {files} WHERE nid=%d", $node->nid);
   $node->images = array();
+  $result = db_query("SELECT i.image_size, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
   while ($file = db_fetch_object($result)) {
-    $node->images[$file->filename] = $file->filepath;
+    $node->images[$file->image_size] = $file->filepath;
   }
   // If the image is smaller than the thumbnail and prevew images, we just use
   // original image rather than creating new deriviatives.
@@ -557,24 +576,51 @@
  * Implementation of hook_insert
  */
 function image_insert($node) {
-  foreach (_image_get_sizes() as $key => $size_info) {
-    _image_insert($node, $key, $node->images[$key]);
-  }
+  _image_save($node);
 }
 
 /**
  * Implementation of hook_update
  */
 function image_update($node) {
-  foreach (_image_get_sizes() as $key => $size) {
-    $old_file = db_fetch_object(db_query("SELECT fid, filepath FROM {files} WHERE filename='%s' AND nid=%d", $key, $node->nid));
+  if ($node->new_file || $node->rebuild_images) {
+    if ($node->new_file) {
+      // The derivative images were built during image_prepare() or 
+      // image_create_node_from() so all we need to do is remove the old images 
+      // save the derivatives with _image_save().
+
+      // Remove all the existing images. 
+      $result = db_query("SELECT f.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
+      while ($file = db_fetch_object($result)) {
+        file_delete(file_create_path($file->filepath));
+        db_queryd("DELETE FROM {files} WHERE fid = %d", $file->fid);
+      }
+      db_queryd("DELETE FROM {image} WHERE nid = %d", $node->nid); 
+
+      _image_save($node);
+    }
+    else if ($node->rebuild_images) {
+      // Find the original image.
+      $original_file = db_fetch_object(db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND image_size = '%s'", $node->nid, IMAGE_ORIGINAL));
+
+      // Delete all but the original image.
+      $result = db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND f.fid <> %d", $node->nid, $original_file->fid);
+      while ($file = db_fetch_object($result)) {
+        // Beware of derivative images that have the same path as the original.
+        if ($file->filepath != $original_file->filepath) {
+          file_delete(file_create_path($file->filepath));
+        }
+        db_queryd("DELETE FROM {files} WHERE fid = %d", $file->fid);
+        db_queryd("DELETE FROM {image} WHERE fid = %d", $file->fid);
+      }
 
-    // This is a new image.
-    if (!isset($node->images[$key]) || $old_file->filepath != $node->images[$key]) {
-      file_delete(file_create_path($old_file->filepath));
-      db_query("DELETE FROM {files} WHERE fid = %d", $old_file->fid);
-      db_query("DELETE FROM {file_revisions} WHERE fid = %d", $old_file->fid);
-      _image_insert($node, $key, $node->images[$key]);
+      _image_build_derivatives($node);
+      drupal_set_message(t('The derivative images for <a href="!link">%title</a> have been regenerated.', array('!link' => url('node/'. $node->nid), '%title' => $node->title)));
+      
+      // Save all but the original image.
+      $sizes = image_get_sizes();
+      unset($sizes[IMAGE_ORIGINAL]);
+      _image_save($node, array_keys($sizes));
     }
   }
 }
@@ -583,12 +629,12 @@
  * Implementation of hook_delete.
  */
 function image_delete($node) {
-  $result = db_query('SELECT fid, filepath FROM {files} WHERE nid = %d', $node->nid);
+  $result = db_query('SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d', $node->nid);
   while ($file = db_fetch_object($result)) {
     file_delete(file_create_path($file->filepath));
-    db_query("DELETE FROM {file_revisions} WHERE fid = %d", $file->fid);
+    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
   }
-  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
+  db_query("DELETE FROM {image} WHERE nid = %d", $node->nid);
 }
 
 /**
@@ -600,10 +646,8 @@
       (!file_exists(file_create_path($node->images[$label])) ||
        filemtime(file_create_path($node->images[$label])) < variable_get('image_updated', 0)))
   {
-    _image_remove_derivatives($node);
-    _image_build_derivatives($node);
+    $node->rebuild_images = TRUE;
     node_save($node);
-    drupal_set_message(t('The image %title had missing derivative image which has been regenerated.', array('%title' => $node->title)));
   }
 
   if (empty($node->images[$label])) {
@@ -740,7 +784,7 @@
   }
   $aspect_ratio = $image_info['height'] / $image_info['width'];
 
-  foreach (_image_get_sizes(NULL, $aspect_ratio) as $key => $size) {
+  foreach (image_get_sizes(NULL, $aspect_ratio) as $key => $size) {
     // Skip over the original.
     if ($key == IMAGE_ORIGINAL) {
       continue;
@@ -766,19 +810,6 @@
   }
 }
 
-function _image_remove_derivatives($node) {
-  $original_file = db_fetch_object(db_query("SELECT * FROM {files} WHERE nid=%d AND filename = '%s'", $node->nid, IMAGE_ORIGINAL));
-  $result = db_query("SELECT * FROM {files} WHERE nid=%d AND fid <> %d", $node->nid, $original_file->fid);
-  while ($file = db_fetch_object($result)) {
-    // Never delete the original!
-    if ($file->filepath != $original_file->filepath) {
-      file_delete(file_create_path($file->filepath));
-    }
-  }
-  db_query("DELETE FROM {files} WHERE nid = %d AND fid <> %d", $node->nid, $original_file->fid);
-  db_query("DELETE FROM {file_revisions} WHERE vid = %d AND fid <> %d", $node->vid, $original_file->fid);
-}
-
 /**
  * Creates an image filename.
  */
@@ -820,7 +851,7 @@
  *   If a $size parameter was specified and it cannot be found FALSE will be 
  *   returned.
  */
-function _image_get_sizes($size = NULL, $aspect_ratio = NULL) {
+function image_get_sizes($size = NULL, $aspect_ratio = NULL) {
   $defaults = array(
     IMAGE_ORIGINAL => array('width' => '', 'height' => '', 'label' => t('Original'), 'link' => IMAGE_LINK_SHOWN),
     IMAGE_THUMBNAIL => array('width' => 100, 'height' => 100, 'label' => t('Thumbnail'), 'link' => IMAGE_LINK_SHOWN),
@@ -855,9 +886,18 @@
 }
 
 /**
+ * Helper function to preserve backwards compatibility. This has been
+ * deprecated in favor of image_get_sizes(). 
+ */
+function _image_get_sizes($size = NULL, $aspect_ratio = NULL) {
+  return image_get_sizes($size, $aspect_ratio);
+}
+
+
+/**
  * Is a given size a built-in, required size?
  * @param $size
- *   On of the keys in the array returned by _image_get_sizes().
+ *   On of the keys in the array returned by image_get_sizes().
  * @return boolean
  */
 function _image_is_required_size($size) {
@@ -865,33 +905,44 @@
 }
 
 /**
- * Moves temporary (working) images to the final directory and stores
- * relevant information in the files table
- */
-function _image_insert(&$node, $size, $image_path) {
-  // Make sure there's an image.
-  if (empty($image_path)) {
-    return;
+ * Moves temporary images to the final directory and stores relevant 
+ * information in the image and files tables.
+ * 
+ * @param $node
+ *   The image node object.
+ * @param $sizes
+ *   Optional array of keys from image_get_sizes() to save the sizes of. If this is not provided then all sizes will be saved.
+ */
+function _image_save(&$node, $sizes = NULL) {
+  if (!isset($sizes)) {
+    $sizes = array_keys(image_get_sizes());
   }
 
   $original_path = $node->images[IMAGE_ORIGINAL];
 
-  // Don't duplicate images when a derivative == _original
-  if (($size != IMAGE_ORIGINAL) && (realpath($image_path) == realpath($original_path))) {
-    return;
-  }
+  foreach ($sizes as $size) {
+    // Make sure there's an image.
+    if (empty($node->images[$size])) {
+      continue;
+    }
+    $image_path = $node->images[$size];
+    // Don't duplicate images when a derivative == _original
+    if ($size != IMAGE_ORIGINAL && $image_path == $original_path) {
+      continue;
+    }
 
-  if (file_move($image_path, _image_filename($original_path, $size))) {
-    // Update the node to reflect the actual filename, it may have been changed
-    // if a file of the same name already existed.
-    $node->images[$size] = $image_path;
-
-    $image_info = image_get_info($image_path);
-    $fid = db_next_id('{files}_fid');
-    db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', '%s')",
+    if (file_move($image_path, _image_filename($original_path, $size))) {
+      // Update the node to reflect the actual filename, it may have been changed
+      // if a file of the same name already existed.
+      $node->images[$size] = $image_path;
+
+      $image_info = image_get_info($image_path);
+      $fid = db_next_id('{files}_fid');
+      db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', '%s')",
              $fid, $node->nid, $size, $image_path, $image_info['mime_type'], $image_info['file_size']);
-    db_query("INSERT INTO {file_revisions} (fid, vid, description, list) VALUES (%d, %d, '%s', %d)",
-             $fid, $node->vid, '', 0);
+      db_query("INSERT INTO {image} (nid, image_size, fid) VALUES (%d, '%s', %d)",
+             $node->nid, $size, $fid);
+    }
   }
 }
 
@@ -948,7 +999,7 @@
 
   // Resize the original.
   $aspect_ratio = $image_info['height'] / $image_info['width'];
-  $original_size = _image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
+  $original_size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
   if (!empty($original_size['width']) && !empty($original_size['height'])) {
     if (image_scale($file->filepath, $file->filepath, $original_size['width'], $original_size['height'])) {
       clearstatcache();
Index: views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/Attic/views.inc,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 views.inc
--- views.inc	10 Jul 2007 23:08:33 -0000	1.1.2.1
+++ views.inc	11 Jul 2007 16:53:32 -0000
@@ -4,7 +4,7 @@
  * Implementation of hook_views_tables()
  */
 function image_views_tables() {
-  $tables['image'] = array(
+  $tables['image_node'] = array(
     'name' => 'node',
     'fields' => array(
       'nid' => array(
@@ -22,6 +22,66 @@
       ),
     ),
   );
+  $tables['image_image'] = array(
+    'name' => 'image',
+    'join' => array(
+      'left' => array(
+        'table' => 'node',
+        'field' => 'nid',
+      ),
+      'right' => array(
+        'field' => 'nid',
+      ),
+    ),
+    'fields' => array(
+      'fid' => array(
+        'name' => t('Image: File Id'),
+        'sortable' => true,
+        'help' => t('File Id which represents the file.'),
+      ),
+    ),
+  );
+  $tables['image_files'] = array(
+    'name' => 'files',
+    'join' => array(
+      'left' => array(
+        'table' => 'image_image',
+        'field' => 'fid',
+      ),
+      'right' => array(
+        'field' => 'fid'
+      ),
+    ),
+    'fields' => array(
+      'filename' => array(
+        'name' => t('Image: File name'),
+        'handler' => array(
+          'views_handler_file_filename' => t('Plain'),
+          'views_handler_file_filename_download' => t('With download link'),
+        ),
+        'sortable' => true,
+        'addlfields' => array('filepath'),
+        'option' => 'string',
+        'help' => t('Display file name'),
+      ),
+      'filepath' => array(
+        'name' => t('Image: File path'),
+        'sortable' => false,
+        'help' => t('Display Path to File.'),
+      ),
+      'filesize' => array(
+        'name' => t('Image: File size'),
+        'handler' => 'views_handler_file_size',
+        'sortable' => true,
+        'help' => t('Display the file size of the associated file.'),
+      ),
+      'filemime' => array(
+        'name' => t('Image: Mime type'),
+        'sortable' => true,
+        'help' => t('This filter allows nodes to be filtered by mime type.'),
+      ),
+    ),
+  );
   return $tables;
 }
 
Index: contrib/image_attach/image_attach.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/contrib/image_attach/Attic/image_attach.module,v
retrieving revision 1.9.2.15
diff -u -r1.9.2.15 image_attach.module
--- contrib/image_attach/image_attach.module	4 Jul 2007 04:40:32 -0000	1.9.2.15
+++ contrib/image_attach/image_attach.module	11 Jul 2007 18:36:07 -0000
@@ -68,7 +68,7 @@
     _image_check_settings();
 
     $image_sizes = array(IMAGE_ATTACH_HIDDEN => t('<Hidden>'));
-    foreach (_image_get_sizes() as $key => $size) {
+    foreach (image_get_sizes() as $key => $size) {
       $image_sizes[$key] = $size['label'];
     }
     
Index: contrib/image_gallery/image_gallery.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/contrib/image_gallery/Attic/image_gallery.module,v
retrieving revision 1.5.2.8
diff -u -r1.5.2.8 image_gallery.module
--- contrib/image_gallery/image_gallery.module	4 Jul 2007 06:38:05 -0000	1.5.2.8
+++ contrib/image_gallery/image_gallery.module	11 Jul 2007 18:34:07 -0000
@@ -328,7 +328,7 @@
   drupal_add_css(drupal_get_path('module', 'image_gallery') .'/image_gallery.css');
 
   // We'll add height to keep thumbnails lined up.
-  $size = _image_get_sizes(IMAGE_THUMBNAIL);
+  $size = image_get_sizes(IMAGE_THUMBNAIL);
   $width = $size['width'];
   $height = $size['height'];
 
