diff --git a/file_entity.file.inc b/file_entity.file.inc index 9921b37..15df41e 100644 --- a/file_entity.file.inc +++ b/file_entity.file.inc @@ -32,6 +32,9 @@ function file_entity_file_insert($file) { // Clear the page and block caches. cache_clear_all(); + + // Get and store image dimensions. + file_entity_image_dimensions($file, TRUE); } /** @@ -46,6 +49,9 @@ function file_entity_file_update($file) { // Clear the page and block caches. cache_clear_all(); + + // Get and store image dimensions. + file_entity_image_dimensions($file, TRUE); } /** @@ -60,6 +66,9 @@ function file_entity_file_delete($file) { // Clear the page and block caches. cache_clear_all(); + + // Delete image dimensions from the {image_dimensions} table + db_query('DELETE FROM {image_dimensions} WHERE fid = :fid', array(':fid' => $file->fid)); } /** @@ -109,3 +118,80 @@ function file_entity_file_operation_info() { return $info; } + +/** + * Implements hook_file_load(). + */ +function file_entity_file_load($files) { + // Load images dimensions already in the {image_dimensions} table. + $result = db_query('SELECT * FROM {image_dimensions} id WHERE id.fid IN (:fids)', array(':fids' => array_keys($files))); + foreach ($result as $record) { + $files[$record->fid]->image_dimensions = array( + 'width' => $record->width, + 'height' => $record->height, + ); + } + // Retrieve any missing images dimensions. + foreach ($files as $file) { + file_entity_image_dimensions($file, FALSE); + } +} + +/** + * Retrieve the dimensions of an image file and store them in the + * {image dimensions} table. + * + * @param $file + * A file object. + * + * @param $force + * TRUE if the image dimensions should always be loaded from the actual file + * even if $file->image_dimensions is already set. + * + * @return + * The image dimensions as an array with the 'width' and 'height' properties. + * The array is also added to $file as its image_dimensions property. If the + * image dimensions cannot be read, the 'width' and 'height' properties will + * be NULL. If $file is either empty or not an image file, FALSE is returned. + */ +function file_entity_image_dimensions($file, $force = FALSE) { + // Prevent PHP notices when trying to read empty files. + // @see http://drupal.org/node/681042 + if (!filesize($file->uri)) { + return; + } + + // Do not bother proceeding if this file does not have an image mime type. + if (strpos($file->filemime, 'image/') !== 0) { + return; + } + + // Return the existing $file->image_dimensions unless a reload is forced. + if (!$force && isset($file->image_dimensions)) { + return $file->image_dimensions; + } + + // We have a non-empty image file. + $image_info = image_get_info($file->uri); + if ($image_info) { + $file->image_dimensions = array( + 'width' => $image_info['width'], + 'height' => $image_info['height'], + ); + // Delete image dimensions from the {image_dimensions} table if it's already there. + db_query('DELETE FROM {image_dimensions} WHERE fid = :fid', array(':fid' => $file->fid)); + // Insert the image dimensions into the {image_dimensions} table. + db_query('INSERT INTO {image_dimensions} (fid, width, height) VALUES (:fid, :width, :height);', array( + ':fid' => $file->fid, + ':width' => $file->image_dimensions['width'], + ':height' => $file->image_dimensions['height'], + )); + } + else { + // Fallback to NULL values. + $file->image_dimensions = array( + 'width' => NULL, + 'height' => NULL, + ); + } +} diff --git a/file_entity.install b/file_entity.install index 8fecc7b..305b80b 100644 --- a/file_entity.install +++ b/file_entity.install @@ -63,7 +63,38 @@ function file_entity_schema() { ), ), ); - + $schema['image_dimensions'] = array( + 'description' => 'Cache images dimensions.', + 'fields' => array( + 'fid' => array( + 'description' => 'File ID.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'height' => array( + 'description' => 'The height of the image in pixels.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'width' => array( + 'description' => 'The width of the image in pixels..', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('fid'), + 'foreign keys' => array( + 'file_managed' => array( + 'table' => 'file_managed', + 'columns' => array('fid' => 'fid'), + ), + ), + ); return $schema; } @@ -247,3 +278,42 @@ function file_entity_update_7104() { } } } + +/** + * Create the {image_dimensions} database table. + */ +function file_entity_update_7200() { + $schema['image_dimensions'] = array( + 'description' => 'Cache images dimensions.', + 'fields' => array( + 'fid' => array( + 'description' => 'File ID.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'height' => array( + 'description' => 'The height of the image in pixels.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'width' => array( + 'description' => 'The width of the image in pixels..', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('fid'), + 'foreign keys' => array( + 'file_managed' => array( + 'table' => 'file_managed', + 'columns' => array('fid' => 'fid'), + ), + ), + ); + db_create_table('image_dimensions', $schema['image_dimensions']); +} diff --git a/file_entity.module b/file_entity.module index 979f413..3df6f0e 100644 --- a/file_entity.module +++ b/file_entity.module @@ -531,22 +531,22 @@ function file_entity_file_formatter_file_image_view($file, $display, $langcode) $scheme = file_uri_scheme($file->uri); $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL); - if (isset($local_wrappers[$scheme]) && $image = image_load($file->uri)) { + if (isset($local_wrappers[$scheme]) && isset($file->image_dimensions)) { if (!empty($display['settings']['image_style'])) { $element = array( '#theme' => 'image_style', '#style_name' => $display['settings']['image_style'], '#path' => $file->uri, - '#width' => $image->info['width'], - '#height' => $image->info['height'], + '#width' => $file->image_dimensions['width'], + '#height' => $file->image_dimensions['height'], ); } else { $element = array( '#theme' => 'image', '#path' => $file->uri, - '#width' => $image->info['width'], - '#height' => $image->info['height'], + '#width' => $file->image_dimensions['width'], + '#height' => $file->image_dimensions['height'], ); } return $element; diff --git a/tests/file_entity.test b/tests/file_entity.test index 94abcea..6401743 100644 --- a/tests/file_entity.test +++ b/tests/file_entity.test @@ -61,6 +61,52 @@ class FileEntityUnitTestCase extends FileEntityTestHelper { $uri = entity_uri('file', $file); $this->assertEqual($uri['path'], "file/{$file->fid}"); } + + function testImageDimensions() { + $images_dimensions = array(); + $text_fids = array(); + // Test hook_file_insert(). + // Files have been saved as part of setup (in FileEntityTestHelper::setUpFiles). + foreach ($this->files['image'] as $file) { + $images_dimensions[$file->fid] = $file->image_dimensions; + $this->assertTrue(isset($file->image_dimensions), 'Image dimensions retrieved on file_save() for an image file.'); + } + foreach ($this->files['text'] as $file) { + $text_fids[] = $file->fid; + $this->assertFalse(isset($file->image_dimensions), 'No image dimensions retrieved on file_save() for an text file.'); + } + + // Test hook_file_load(). + // Clear the cache and load fresh files objects to test file_load behavior. + entity_get_controller('file')->resetCache(); + foreach (file_load_multiple(array_keys($images_dimensions)) as $file) { + $this->assertTrue(isset($file->image_dimensions), 'Image dimensions retrieved on file_load() for an image file.'); + $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'], 'Loaded image height is equal to saved image height.'); + $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'], 'Loaded image width is equal to saved image width.'); + } + foreach (file_load_multiple($text_fids) as $file) { + $this->assertFalse(isset($file->image_dimensions), 'No image dimensions retrieved on file_load() for an text file.'); + } + + // Test hook_file_update(). + // Load the first image file and resize it. + $file = file_load(reset(array_keys($images_dimensions))); + $image = image_load($file->uri); + image_resize($image, $file->image_dimensions['width'] / 2, $file->image_dimensions['height'] / 2); + image_save($image); + file_save($file); + $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'] / 2, 'Image file height updated by file_save().'); + $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'] / 2, 'Image file width updated by file_save().'); + // Clear the cache and reload the file. + entity_get_controller('file')->resetCache(); + $file = file_load($file->fid); + $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'] / 2, 'Updated image height retrieved by file_load().'); + $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'] / 2, 'Updated image width retrieved by file_load().'); + + //Test hook_file_delete(). + file_delete($file, TRUE); + $this->assertFalse(db_query('SELECT count(*) FROM {image_dimensions} WHERE fid = :fid', array(':fid' => 'fid'))->fetchField(), 'Row deleted in {file_dimensions} on file_delete().'); + } } class FileEntityTokenTestCase extends FileEntityTestHelper {