Index: image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/image.module,v
retrieving revision 1.301
diff -u -p -r1.301 image.module
--- image.module	8 Mar 2009 21:40:38 -0000	1.301
+++ image.module	9 Mar 2009 02:11:03 -0000
@@ -172,66 +172,6 @@ function image_operations_rebuild($nids)
 }
 
 /**
- * TODO: document me...
- */
-function image_node_form_submit($form, &$form_state) {
-  // 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 = $form_state['values']['nid'] ? $form_state['values']['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]);
-  }
-
-  // Validators for file_save_upload().
-  $validators = array(
-    'file_validate_is_image' => array(),
-  );
-
-  if ($file = file_save_upload('image', $validators)) {
-    // Resize the original.
-    $image_info = image_get_info($file->filepath);
-    $aspect_ratio = $image_info['height'] / $image_info['width'];
-    $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) {
-        clearstatcache();
-        $file->filesize = filesize($file->filepath);
-        drupal_set_message(t('The original image was resized to fit within the maximum allowed resolution of %width x %height pixels.', array('%width' => $original_size['width'], '%height' => $original_size['height'])));
-      }
-    }
-
-    // Check the file size limit.
-    if ($file->filesize > variable_get('image_max_upload_size', 800) * 1024) {
-      form_set_error('image', t('The image you uploaded was too big. You are only allowed upload files less than %max_size but your file was %file_size.', array('%max_size' => format_size(variable_get('image_max_upload_size', 800) * 1024), '%file_size' => format_size($file->filesize))));
-      file_delete($file->filepath);
-      return;
-    }
-
-    // We're good to go.
-    $form_state['values']['images'][IMAGE_ORIGINAL] = $file->filepath;
-    $form_state['values']['rebuild_images'] = FALSE;
-    $form_state['values']['new_file'] = TRUE;
-
-    // Call hook to allow other modules to modify the original image.
-    module_invoke_all('image_alter', $form_state['values'], $file->filepath, IMAGE_ORIGINAL);
-    $form_state['values']['images'] = _image_build_derivatives((object) $form_state['values'], TRUE);
-
-    // Store the new file into the session.
-    $_SESSION['image_new_files'][$nid] = $form_state['values']['images'];
-  }
-  // Reload new files uploaded in a previous preview.
-  else if (isset($_SESSION['image_new_files'][$nid])) {
-    $form_state['values']['images'] = $_SESSION['image_new_files'][$nid];
-  }
-}
-
-/**
  * Implementation of hook_file_download().
  *
  * Note that in Drupal 5, the upload.module's hook_file_download() checks its
@@ -357,14 +297,19 @@ function image_form_add_thumbnail($form,
 }
 
 /**
- * Implementation of hook_form
+ * Implementation of hook_form().
  */
-function image_form(&$node, &$param) {
+function image_form(&$node, $form_state) {
   _image_check_settings();
 
+  if (!$_POST && !empty($_SESSION['image_upload'])) {
+    unset($_SESSION['image_upload']);
+  }
+
   $type = node_get_types('type', $node);
 
-  $form['#submit'][] = 'image_node_form_submit';
+  $form['#validate'][] = 'image_form_validate';
+  $form['#submit'][] = 'image_form_submit';
 
   $form['title'] = array(
     '#type' => 'textfield',
@@ -401,7 +346,7 @@ function image_form(&$node, &$param) {
     '#access' => (!isset($node->nid) ? FALSE : TRUE),
   );
 
-  $form['#attributes'] = array("enctype" => "multipart/form-data");
+  $form['#attributes'] = array('enctype' => 'multipart/form-data');
   $form['image'] = array(
     '#type' => 'file',
     '#title' => t('Image'),
@@ -416,10 +361,66 @@ function image_form(&$node, &$param) {
   return $form;
 }
 
-function image_validate($node) {
-  $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_form_validate($form, &$form_state) {
+  // Avoid blocking node deletion with missing image.
+  if ($form_state['values']['op'] == t('Delete')) {
+    return;
+  }
+
+  // Validators for file_save_upload().
+  $validators = array(
+    'file_validate_is_image' => array(),
+  );
+
+  // New image uploads need to be saved in images/temp in order to be viewable
+  // during node preview.
+  $temporary_file_path = file_create_path(file_directory_path() . '/' . variable_get('image_default_path', 'images') .'/temp');
+
+  if ($file = file_save_upload('image', $validators, $temporary_file_path)) {
+    // Resize the original.
+    $image_info = image_get_info($file->filepath);
+    $aspect_ratio = $image_info['height'] / $image_info['width'];
+    $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) {
+        clearstatcache();
+        $file->filesize = filesize($file->filepath);
+        drupal_set_message(t('The original image was resized to fit within the maximum allowed resolution of %width x %height pixels.', array('%width' => $original_size['width'], '%height' => $original_size['height'])));
+      }
+    }
+
+    // Check the file size limit.
+    if ($file->filesize > variable_get('image_max_upload_size', 800) * 1024) {
+      form_set_error('image', t('The image you uploaded was too big. You are only allowed upload files less than %max_size but your file was %file_size.', array('%max_size' => format_size(variable_get('image_max_upload_size', 800) * 1024), '%file_size' => format_size($file->filesize))));
+      file_delete($file->filepath);
+      return;
+    }
+
+    // We're good to go.
+    $form_state['values']['images'][IMAGE_ORIGINAL] = $file->filepath;
+    $form_state['values']['rebuild_images'] = FALSE;
+    $form_state['values']['new_file'] = TRUE;
+
+    // Call hook to allow other modules to modify the original image.
+    module_invoke_all('image_alter', $form_state['values'], $form_state['values']['images'][IMAGE_ORIGINAL], IMAGE_ORIGINAL);
+    $form_state['values']['images'] = _image_build_derivatives((object) $form_state['values'], TRUE);
+
+    // Store the new file into the session.
+    $_SESSION['image_upload'] = $form_state['values']['images'];
+  }
+  elseif (empty($form_state['values']['images'][IMAGE_ORIGINAL])) {
+    if (empty($_SESSION['image_upload'])) {
+      form_set_error('image', t('You must upload an image.'));
+    }
+  }
+}
+
+function image_form_submit($form, &$form_state) {
+  if (!empty($_SESSION['image_upload'])) {
+    $form_state['values']['images'] = $_SESSION['image_upload'];
+    $form_state['values']['new_file'] = TRUE;
+    unset($_SESSION['image_upload']);
   }
 }
 
Index: tests/image.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/tests/image.test,v
retrieving revision 1.3
diff -u -p -r1.3 image.test
--- tests/image.test	1 Feb 2009 03:10:34 -0000	1.3
+++ tests/image.test	9 Mar 2009 02:21:46 -0000
@@ -8,7 +8,7 @@ class ImageTestCase extends DrupalWebTes
   protected $web_user;
   protected $image;
   protected $another_image;
-  
+
   function getInfo() {
     return array(
       'name' => t('Image functionality'),
@@ -19,14 +19,14 @@ class ImageTestCase extends DrupalWebTes
 
   function setUp() {
     parent::setUp('image');
-    
+
     // User to create images.
     $this->web_user = $this->drupalCreateUser(array('create images', 'view original images', 'edit own images', 'edit images'));
     $this->drupalLogin($this->web_user);
-    
+
     // Uploadable image.
-    $this->image = 'misc' . DIRECTORY_SEPARATOR . 'druplicon.png';
-    $this->another_image = 'misc' . DIRECTORY_SEPARATOR . 'throbber.gif';
+    $this->image = 'misc/druplicon.png';
+    $this->another_image = 'misc/throbber.gif';
 
     // Set small dimensions for testing scale so $this->image is small enough.
     $image_sizes = image_get_sizes();
@@ -39,11 +39,33 @@ class ImageTestCase extends DrupalWebTes
     $image_sizes['preview']['height'] = 10;
     variable_set('image_sizes', $image_sizes);
   }
-  
+
+  /**
+   * Clean-up temporary files.
+   *
+   * @see system_cron()
+   *
+   * @todo This lets SimpleTest die for any reason.
+   */
+  function __tearDown() {
+    $result = db_query('SELECT * FROM {files} WHERE status = %d', FILE_STATUS_TEMPORARY);
+    while ($file = db_fetch_object($result)) {
+      if (file_exists($file->filepath)) {
+        // If files that exist cannot be deleted, continue so the database remains
+        // consistent.
+        if (!file_delete($file->filepath)) {
+          $this->error(t('Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath)));
+          continue;
+        }
+      }
+      db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+    }
+  }
+
   /**
    * Verify creating/displaying/editing/deleting image nodes.
    */
-  function testNode() {
+  function testImageNode() {
     // Create an image.
     $edit = array(
       'title' => $this->randomName(),
@@ -51,46 +73,160 @@ class ImageTestCase extends DrupalWebTes
       'files[image]' => realpath($this->image),
     );
     $this->drupalPost('node/add/image', $edit, t('Save'));
-    
-    $this->assertRaw(t('@type %title has been created.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image created.'));
-    
+
+    $this->assertRaw(t('@type %title has been created.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image node was created.'));
+
     $node = node_load(array('title' => $edit['title']));
-    $this->assertTrue($node, t('Image found in database.'));
+    $this->assertTrue($node, t('Image node is found in database.'));
 
     // Display an image.
+    $this->drupalGet('node/' . $node->nid, array('query' => 'size=_original'));
+    $this->assertPattern('@<img[^>]+?' . $node->images['_original'] . '[^>]+?>@', t('Original image displayed on the page.'));
+    $this->assertTrue(file_exists($node->images['_original']), t('Original image exists.'));
+
     $this->drupalGet('node/' . $node->nid);
     $this->assertPattern('@<img[^>]+?' . $node->images['preview'] . '[^>]+?>@', t('Image preview displayed on the page.'));
     $this->assertTrue(file_exists($node->images['preview']), t('Image preview exists.'));
 
-    $this->drupalGet('node/' . $node->nid, array('query' => 'size=_original'));
-    $this->assertPattern('@<img[^>]+?' . $node->images['_original'] . '[^>]+?>@', t('Original image displayed on the page.'));
-    $this->assertTrue(file_exists($node->images['_original']), t('Original image exists.'));
-    
     $this->drupalGet('node/' . $node->nid, array('query' => 'size=thumbnail'));
     $this->assertPattern('@<img[^>]+?' . $node->images['thumbnail'] . '[^>]+?>@', t('Image thumbnail displayed on the page.'));
     $this->assertTrue(file_exists($node->images['thumbnail']), t('Image thumbnail exists.'));
-    
+
     // Edit an image.
     $another_edit = array(
       'title' => $edit['title'],
       'files[image]' => realpath($this->another_image),
     );
-    $this->drupalPost('node/' . $node->nid .'/edit', $another_edit, t('Save'));
+    $this->drupalPost('node/' . $node->nid . '/edit', $another_edit, t('Save'));
     $another_node = node_load(array('title' => $edit['title']));
-    $this->assertFalse(file_exists($node->images['preview']) || file_exists($node->images['_original']) || file_exists($node->images['thumbnail']), t('The previous image deleted.'));
-    
+    $this->assertFalse(file_exists($node->images['preview']) || file_exists($node->images['_original']) || file_exists($node->images['thumbnail']), t('Previous image derivative files were deleted.'));
+
     // Delete an image.
-    $this->drupalPost('node/' . $node->nid .'/delete', array(), t('Delete'));
-    $this->assertRaw(t('@type %title has been deleted.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image created.'));
+    $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete'));
+    $this->assertRaw(t('@type %title has been deleted.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image node was deleted.'));
+    $node = node_load(array('title' => $edit['title']));
+    $this->assertFalse($node, t('Image node is not found in database.'));
+    $this->assertFalse(file_exists($another_node->images['preview']) || file_exists($another_node->images['_original']) || file_exists($another_node->images['thumbnail']), t('Image file was deleted.'));
+  }
+
+  /**
+   * Verify that images cannot be created without a file.
+   */
+  function testImageNodeValidation() {
+    // Create an image node without image file.
+    $edit = array(
+      'title' => $this->randomName(),
+      'body' => $this->randomName(),
+    );
+    $this->drupalPost('node/add/image', $edit, t('Save'));
+    $this->assertRaw(t('You must upload an image.'), t('Refused node creation without image.'));
+
+    $node = node_load(array('title' => $edit['title']));
+    $this->assertFalse($node, t('Image node is not found in database.'));
+  }
+
+  /**
+   * Verify image previews and proper submission.
+   */
+  function testImageNodePreview() {
+    // Setup POST data for image.
+    $edit = array(
+      'title' => $this->randomName(),
+      'body' => $this->randomName(),
+      'files[image]' => realpath($this->image),
+    );
+
+    // Preview image node.
+    $this->drupalGet('node/add/image');
+    $this->drupalPost(NULL, $edit, t('Preview'));
+
+    // Save image node preview as is.
+    $this->drupalPost(NULL, NULL, t('Save'));
+
+    // Verify image node was properly created.
+    $node = node_load(array('title' => $edit['title']));
+    $this->assertTrue($node, t('Image node is found in database.'));
+    $this->assertTrue(file_exists($node->images['_original']), t('Original image exists.'));
+    $filename = preg_replace('/_[^\.]+/', '', basename($node->images['_original']));
+    $this->assertTrue($filename == basename($this->image), t('Image file was properly stored.'));
+  }
+
+  /**
+   * Verify that images can be created in parallel (session handling).
+   *
+   * @todo Doesn't look valid. Can SimpleTest support this at all?
+   * @todo testImageCreateNodeFrom() fails is this test is run.
+   */
+  function __testImageNodeValidationAsync() {
+    // Setup POST data for first image.
+    $edit1 = array(
+      'title' => $this->randomName(),
+      'body' => $this->randomName(),
+      'files[image]' => realpath($this->image),
+    );
+    // Setup POST data for second image.
+    $edit2 = array(
+      'title' => $this->randomName(),
+      'body' => $this->randomName(),
+      'files[image]' => realpath($this->another_image),
+    );
+
+    // Preview the first image node.
+    $this->drupalGet('node/add/image');
+    $this->drupalPost(NULL, $edit1, t('Preview'));
+
+    // Preview the second image node.
+    $this->drupalGet('node/add/image');
+    $this->drupalPost(NULL, $edit2, t('Preview'));
+
+    // Save the first image node.
+    $this->drupalPost(NULL, $edit1, t('Save'));
+    // Save the second image node.
+    $this->drupalPost('node/add/image', $edit2, t('Save'));
+
+    // Verify first image node contains the first image.
+    $node = node_load(array('title' => $edit1['title']));
+    $filename = preg_replace('/_[^\.]+/', '', basename($node->images['_original']));
+    $this->assertTrue($filename == basename($this->image), t('First image was properly submitted.'));
+
+    // Verify second image node contains the second image.
+    $node = node_load(array('title' => $edit2['title']));
+    $filename = preg_replace('/_[^\.]+/', '', basename($node->images['_original']));
+    $this->assertTrue($filename == basename($this->another_image), t('Second image was properly submitted.'));
+  }
+
+  /**
+   * Verify that images with missing file information can be deleted from edit form.
+   */
+  function testImageNodeDeletion() {
+    // Create an image.
+    $edit = array(
+      'title' => $this->randomName(),
+      'body' => $this->randomName(),
+      'files[image]' => realpath($this->image),
+    );
+
+    // Create image.
+    $this->drupalPost('node/add/image', $edit, t('Save'));
+    $this->assertRaw(t('@type %title has been created.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image node was created.'));
     $node = node_load(array('title' => $edit['title']));
-    $this->assertFalse($node, t('Image not found in database.'));
-    $this->assertFalse(file_exists($another_node->images['preview']) || file_exists($another_node->images['_original']) || file_exists($another_node->images['thumbnail']), t('Image deleted.'));
+    $this->assertTrue($node, t('Image node is found in database.'));
+
+    // Remove {files} row to create corruption condition.
+    $this->assertTrue(db_query("DELETE f FROM {files} f INNER JOIN {image} i WHERE f.fid = i.fid AND i.nid = %d", $node->nid), t('Image file information was deleted.'));
+    $node = node_load(array('title' => $edit['title']));
+    $this->assertFalse(isset($node->images['_original']), t('Image file information is missing on re-loaded node.'));
+
+    $this->drupalPost('node/' . $node->nid . '/edit', array(), t('Delete'));
+    $this->assertRaw(t('Are you sure you want to delete %title?', array('%title' => $edit['title'])), t('Delete confirmation form is displayed.'));
+    $this->drupalPost(NULL, array(), t('Delete'));
+    $this->assertRaw(t('@type %title has been deleted.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image node was deleted.'));
   }
 
   /**
-   * Test image node creation.
+   * Verify image_create_node_from() works like regular image node creation.
    */
-  function testCreateNode() {
+  function testImageCreateNodeFrom() {
     $edit = array(
       'title' => $this->randomName(),
       'body' => $this->randomName(),
@@ -98,23 +234,28 @@ class ImageTestCase extends DrupalWebTes
     );
     $this->drupalPost('node/add/image', $edit, t('Save'));
     $this->assertRaw(t('@type %title has been created.', array('@type' => 'Image', '%title' => $edit['title'])), t('Image created.'));
-    
+
     $node_post = node_load(array('title' => $edit['title']));
-    $this->assertTrue($node_post, t('Image found in database.'));
+    $this->assertTrue($node_post, t('Form created image node found in database.'));
 
-    // Make a copy of the image so image_create_node_from() deletes original image.
+    // Copy the image, since image_create_node_from() deletes the original image.
     file_copy($edit['files[image]'], file_directory_temp());
     $node_api = image_create_node_from(file_directory_temp() . '/' . basename($edit['files[image]']), $edit['title'], $edit['body']);
-    // Rebuild images.
+    $this->assertTrue(is_object($node_api) && $node_api->nid, t('API created image node found in database.'));
+    // Rebuild derivative images.
     $node_api = node_load($node_api->nid);
 
-    // Check equality of nodes.
-    $equality = 
-      ($node_post->title == $node_api->title) && 
-      (strip_tags($node_post->body) == strip_tags($node_api->body)) &&
-      (filesize($node_post->images['_original']) == filesize($node_api->images['_original'])) &&
-      (filesize($node_post->images['preview']) == filesize($node_api->images['preview'])) &&
-      (filesize($node_post->images['thumbnail']) == filesize($node_api->images['thumbnail']));
-    $this->assertTrue($equality, t('Images nodes are equal.'));
+    // Verify that both nodes are equal.
+    $equality = ($node_post->title == $node_api->title);
+    $this->assertTrue($equality, t('Image node titles are equal.'));
+    $equality = $equality && (strip_tags($node_post->body) == strip_tags($node_api->body));
+    $this->assertTrue($equality, t('Image node bodys are equal.'));
+    $equality = $equality && (filesize($node_post->images['_original']) == filesize($node_api->images['_original']));
+    $this->assertTrue($equality, t('Original images are equal.'));
+    $equality = $equality && (filesize($node_post->images['preview']) == filesize($node_api->images['preview']));
+    $this->assertTrue($equality, t('Preview images are equal.'));
+    $equality = $equality && (filesize($node_post->images['thumbnail']) == filesize($node_api->images['thumbnail']));
+    $this->assertTrue($equality, t('Image nodes are equal.'));
   }
 }
+
