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('@]+?' . $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('@]+?' . $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('@]+?' . $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('@]+?' . $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.')); } } +