/path/to/file.ext. * * Presets can be managed at http://example.com/admin/build/imagecache. * * To view a derivative image you request a special url containing * 'imagecache//path/to/file.ext. * * If you had a preset names 'thumbnail' and you wanted to see the * thumbnail version of http://example.com/files/path/to/myimage.jpg you * would use http://example.com/files/imagecache/thumbnail/path/to/myimage.jpg * * ImageCache provides formatters for CCK Imagefields and is leveraged by several * other modules. ImageCache also relies heavily on ImageAPI for it's image processing. * If there are errors with actual image processing look to ImageAPI first. * * @todo: add watermarking capabilities. * */ /** * Imagecache preset storage constant for user-defined presets in the DB. */ define('IMAGECACHE_STORAGE_NORMAL', 0); /** * Imagecache preset storage constant for module-defined presets in code. */ define('IMAGECACHE_STORAGE_DEFAULT', 1); /** * Imagecache preset storage constant for user-defined presets that override * module-defined presets. */ define('IMAGECACHE_STORAGE_OVERRIDE', 2); /********************************************************************************************* * Drupal Hooks *********************************************************************************************/ /** * Implementation of hook_perm(). */ function imagecache_perm() { $perms = array('administer imagecache', 'flush imagecache'); foreach (imagecache_presets() as $preset) { $perms[] = 'view imagecache '. $preset['presetname']; } return $perms; } /** * Implementation of hook_menu(). */ function imagecache_menu() { $items = array(); // standard imagecache callback. $items[file_directory_path() .'/imagecache'] = array( 'page callback' => 'imagecache_cache', 'access callback' => TRUE, 'type' => MENU_CALLBACK ); // private downloads imagecache callback $items['system/files/imagecache'] = array( 'page callback' => 'imagecache_cache_private', 'access callback' => TRUE, 'type' => MENU_CALLBACK ); return $items; } /** * Implementation of hook_form_FORM_ID_alter. * * Clear imagecache presets cache on admin/build/modules form. */ function imagecache_form_system_modules_alter(&$form, $form_state) { imagecache_presets(TRUE); } /** * Implementation of hook_form_FORM_ID_alter. * * The file system form is modified to include an extra submit handler, so * that imagecache can rebuild the menu after the filesystem path is changed. */ function imagecache_form_system_file_system_settings_alter(&$form, &$form_state) { $form['#submit'][] = 'imagecache_system_file_system_submit'; } /** * Rebuild menus to ensure we've got the right files directory callback. */ function imagecache_system_file_system_submit($form, &$form_state) { menu_rebuild(); } /** * Implementation of hook_theme(). */ function imagecache_theme() { $theme = array( 'imagecache' => array( 'arguments' => array( 'namespace' => NULL, 'path' => NULL, 'alt' => NULL, 'title' => NULL, )), 'imagecache_imagelink' => array( 'arguments' => array( 'namespace' => NULL, 'path' => NULL, 'alt' => NULL, 'title' => NULL, 'attributes' => array(), )), 'imagecache_resize' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_scale' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_scale_and_crop' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_deprecated_scale' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_crop' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_desaturate' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_rotate' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), 'imagecache_sharpen' => array( 'file' => 'imagecache_actions.inc', 'arguments' => array('element' => NULL), ), ); foreach (imagecache_presets() as $preset) { $theme['imagecache_formatter_'. $preset['presetname'] .'_default'] = array( 'arguments' => array('element' => NULL), 'function' => 'theme_imagecache_formatter_default', ); $theme['imagecache_formatter_'. $preset['presetname'] .'_linked'] = array( 'arguments' => array('element' => NULL), 'function' => 'theme_imagecache_formatter_linked', ); $theme['imagecache_formatter_'. $preset['presetname'] .'_imagelink'] = array( 'arguments' => array('element' => NULL), 'function' => 'theme_imagecache_formatter_imagelink', ); $theme['imagecache_formatter_'. $preset['presetname'] .'_path'] = array( 'arguments' => array('element' => NULL), 'function' => 'theme_imagecache_formatter_path', ); $theme['imagecache_formatter_'. $preset['presetname'] .'_url'] = array( 'arguments' => array('element' => NULL), 'function' => 'theme_imagecache_formatter_url', ); } return $theme; } /** * Implementation of hook_imagecache_actions. * * @return array * An array of information on the actions implemented by a module. The array * contains a sub-array for each action node type, with the machine-readable * action name as the key. Each sub-array has up to 3 attributes. Possible * attributes: * * "name": the human-readable name of the action. Required. * "description": a brief description of the action. Required. * "file": the name of the include file the action can be found * in relative to the implementing module's path. */ function imagecache_imagecache_actions() { $actions = array( 'imagecache_resize' => array( 'name' => 'Resize', 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.', 'file' => 'imagecache_actions.inc', ), 'imagecache_scale' => array( 'name' => 'Scale', 'description' => 'Resize an image maintaining the original aspect-ratio (only one value necessary).', 'file' => 'imagecache_actions.inc', ), 'imagecache_deprecated_scale' => array( 'name' => 'Deprecated Scale', 'description' => 'Precursor to Scale and Crop. Has inside and outside dimension support. This action will be removed in ImageCache 2.1).', 'file' => 'imagecache_actions.inc', ), 'imagecache_scale_and_crop' => array( 'name' => 'Scale And Crop', 'description' => 'Resize an image while maintaining aspect ratio, then crop it to the specified dimensions.', 'file' => 'imagecache_actions.inc', ), 'imagecache_crop' => array( 'name' => 'Crop', 'description' => 'Crop an image to the rectangle specified by the given offsets and dimensions.', 'file' => 'imagecache_actions.inc', ), 'imagecache_desaturate' => array( 'name' => 'Desaturate', 'description' => 'Convert an image to grey scale.', 'file' => 'imagecache_actions.inc', ), 'imagecache_rotate' => array( 'name' => 'Rotate', 'description' => 'Rotate an image.', 'file' => 'imagecache_actions.inc', ), 'imagecache_sharpen' => array( 'name' => 'Sharpen', 'description' => 'Sharpen an image using unsharp masking.', 'file' => 'imagecache_actions.inc', ), ); return $actions; } /** * Pull in actions exposed by other modules using hook_imagecache_actions(). * * @param $reset * Boolean flag indicating whether the cached data should be * wiped and recalculated. * * @return * An array of actions to be used when transforming images. */ function imagecache_action_definitions($reset = FALSE) { static $actions; if (!isset($actions) || $reset) { if (!$reset && ($cache = cache_get('imagecache_actions')) && !empty($cache->data)) { $actions = $cache->data; } else { foreach (module_implements('imagecache_actions') as $module) { foreach (module_invoke($module, 'imagecache_actions') as $key => $action) { $action['module'] = $module; if (!empty($action['file'])) { $action['file'] = drupal_get_path('module', $action['module']) .'/'. $action['file']; } $actions[$key] = $action; }; } uasort($actions, '_imagecache_definitions_sort'); cache_set('imagecache_actions', $actions); } } return $actions; } function _imagecache_definitions_sort($a, $b) { $a = $a['name']; $b = $b['name']; if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; } function imagecache_action_definition($action) { static $definition_cache; if (!isset($definition_cache[$action])) { $definitions = imagecache_action_definitions(); $definition = (isset($definitions[$action])) ? $definitions[$action] : array(); if (isset($definition['file'])) { require_once($definition['file']); } $definition_cache[$action] = $definition; } return $definition_cache[$action]; } /** * Return a URL that points to the location of a derivative of the * original image transformed with the given preset. * * Special care is taken to make this work with the possible combinations of * Clean URLs and public/private downloads. For example, when Clean URLs are not * available an URL with query should be returned, like * http://example.com/?q=files/imagecache/foo.jpg, so that imagecache is able * intercept the request for this file. * * This code is very similar to the Drupal core function file_create_url(), but * handles the case of Clean URLs and public downloads differently however. * * @param $presetname * @param $filepath * String specifying the path to the image file. * @param $bypass_browser_cache * A Boolean indicating that the URL for the image should be distinct so that * the visitors browser will not be able to use a previously cached version. * Defaults to FALSE. * @param $absolute * A Boolean indicating that the URL should be absolute. Defaults to TRUE. */ function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = FALSE, $absolute = TRUE) { $path = _imagecache_strip_file_directory($filepath); if (module_exists('transliteration')) { $path = transliteration_get($path); } $args = array('absolute' => $absolute, 'query' => empty($bypass_browser_cache) ? NULL : time()); switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) { case FILE_DOWNLOADS_PUBLIC: $base = $absolute ? $GLOBALS['base_url'] .'/' : ''; return url($base . file_directory_path() ."/imagecache/$presetname/$path", $args); case FILE_DOWNLOADS_PRIVATE: return url("system/files/imagecache/$presetname/$path", $args); } } /** * Return a file system location that points to the location of a derivative * of the original image at @p $path, transformed with the given @p $preset. * Keep in mind that the image might not yet exist and won't be created. */ function imagecache_create_path($presetname, $path) { $path = _imagecache_strip_file_directory($path); return file_create_path() .'/imagecache/'. $presetname .'/'. $path; } /** * Remove a possible leading file directory path from the given path. */ function _imagecache_strip_file_directory($path) { $dirpath = file_directory_path(); $dirlen = strlen($dirpath); if (substr($path, 0, $dirlen + 1) == $dirpath .'/') { $path = substr($path, $dirlen + 1); } return $path; } /** * callback for handling public files imagecache requests. */ function imagecache_cache() { $args = func_get_args(); $preset = check_plain(array_shift($args)); $path = implode('/', $args); _imagecache_cache($preset, $path); } /** * callback for handling private files imagecache requests */ function imagecache_cache_private() { $args = func_get_args(); $preset = check_plain(array_shift($args)); $source = implode('/', $args); if (user_access('view imagecache '. $preset) && !in_array(-1, module_invoke_all('file_download', $source))) { _imagecache_cache($preset, $source); } else { // if there is a 403 image, display it. $accesspath = file_create_path('imagecache/'. $preset .'.403.png'); if (is_file($accesspath)) { imagecache_transfer($accesspath); exit; } header('HTTP/1.0 403 Forbidden'); exit; } } /** * Handle request validation and responses to ImageCache requests. * * @see imagecache_generate_image() if you're writing code that needs to have * ImageCache generate images but not send them to a browser. */ function _imagecache_cache($presetname, $path) { if (!$preset = imagecache_preset_by_name($presetname)) { // Send a 404 if we don't know of a preset. header("HTTP/1.0 404 Not Found"); exit; } // umm yeah deliver it early if it is there. especially useful // to prevent lock files from being created when delivering private files. $dst = imagecache_create_path($preset['presetname'], $path); if (is_file($dst)) { imagecache_transfer($dst); } // preserve path for watchdog. $src = $path; // Check if the path to the file exists. if (!is_file($src) && !is_file($src = file_create_path($src))) { watchdog('imagecache', '404: Unable to find %image ', array('%image' => $src), WATCHDOG_ERROR); header("HTTP/1.0 404 Not Found"); exit; }; // Bail if the requested file isn't an image you can't request .php files // etc... if (!getimagesize($src)) { watchdog('imagecache', '403: File is not an image %image ', array('%image' => $src), WATCHDOG_ERROR); header('HTTP/1.0 403 Forbidden'); exit; } $lockfile = file_directory_temp() .'/'. $preset['presetname'] . basename($src); if (file_exists($lockfile)) { watchdog('imagecache', 'ImageCache already generating: %dst, Lock file: %tmp.', array('%dst' => $dst, '%tmp' => $lockfile), WATCHDOG_NOTICE); // 307 Temporary Redirect, to myself. Lets hope the image is done next time around. header('Location: '. request_uri(), TRUE, 307); exit; } touch($lockfile); // register the shtdown function to clean up lock files. by the time shutdown // functions are being called the cwd has changed from document root, to // server root so absolute paths must be used for files in shutdown functions. register_shutdown_function('file_delete', realpath($lockfile)); // check if deriv exists... (file was created between apaches request handler and reaching this code) // otherwise try to create the derivative. if (file_exists($dst) || imagecache_build_derivative($preset['actions'], $src, $dst)) { imagecache_transfer($dst); } // Generate an error if image could not generate. watchdog('imagecache', 'Failed generating an image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset['presetname']), WATCHDOG_ERROR); header("HTTP/1.0 500 Internal Server Error"); exit; } /** * Apply an action to an image. * * @param $action * Action array * @param $image * Image object * @return * Boolean, TRUE indicating success and FALSE failure. */ function _imagecache_apply_action($action, $image) { $actions = imagecache_action_definitions(); if ($definition = imagecache_action_definition($action['action'])) { $function = $action['action'] .'_image'; if (function_exists($function)) { return $function($image, $action['data']); } } // skip undefined actions.. module probably got uninstalled or disabled. watchdog('imagecache', 'non-existant action %action', array('%action' => $action['action']), WATCHDOG_NOTICE); return TRUE; } /** * Helper function to transfer files from imagecache. * * Determines MIME type and sets a last modified header. * * @param $path * String containing the path to file to be transferred. * @return * This function does not return. It calls exit(). */ function imagecache_transfer($path) { $size = getimagesize($path); $headers = array('Content-Type: '. mime_header_encode($size['mime'])); if ($fileinfo = stat($path)) { $headers[] = 'Content-Length: '. $fileinfo[7]; $headers[] = 'Expires: ' . gmdate('D, d M Y H:i:s', time() + 1209600) .' GMT'; $headers[] = 'Cache-Control: max-age=1209600, private, must-revalidate'; _imagecache_cache_set_cache_headers($fileinfo, $headers); } file_transfer($path, $headers); exit; } /** * Set file headers that handle "If-Modified-Since" correctly for the * given fileinfo. * * Note that this function may return or may call exit(). * * Most code has been taken from drupal_page_cache_header(). * * @param $fileinfo * Array returned by stat(). * @param * Array of existing headers. * @return * Nothing but beware that this function may not return. */ function _imagecache_cache_set_cache_headers($fileinfo, &$headers) { // Set default values: $last_modified = gmdate('D, d M Y H:i:s', $fileinfo[9]) .' GMT'; $etag = '"'. md5($last_modified) .'"'; // See if the client has provided the required HTTP headers: $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; if ($if_modified_since && $if_none_match && $if_none_match == $etag // etag must match && $if_modified_since == $last_modified) { // if-modified-since must match header('HTTP/1.1 304 Not Modified'); // All 304 responses must send an etag if the 200 response // for the same object contained an etag header('Etag: '. $etag); // We must also set Last-Modified again, so that we overwrite Drupal's // default Last-Modified header with the right one header('Last-Modified: '. $last_modified); exit; } // Send appropriate response: $headers[] = 'Last-Modified: '. $last_modified; $headers[] = 'ETag: '. $etag; } /** * Create a new image based on an image preset. * * @param $preset * An image preset array. * @param $source * Path of the source file. * @param $destination * Path of the destination file. * @return * TRUE if an image derivative is generated, FALSE if no image * derivative is generated. NULL if the derivative is being generated. */ function imagecache_build_derivative($actions, $src, $dst) { // get the folder for the final location of this preset... $dir = dirname($dst); // Build the destination folder tree if it doesn't already exists. if (!file_check_directory($dir, FILE_CREATE_DIRECTORY) && !mkdir($dir, 0775, TRUE)) { watchdog('imagecache', 'Failed to create imagecache directory: %dir', array('%dir' => $dir), WATCHDOG_ERROR); return FALSE; } // file_check_directory() has an annoying habit of displaying "directory ... // has been created" status messages. To avoid confusing visitors we clear // out all the status messages for non-ImageCache admins. This might affect // some other messages but errors and warnings should still be displayed. if (!user_access('administer imagecache')) { drupal_get_messages('status', TRUE); } // Simply copy the file if there are no actions. if (empty($actions)) { return file_copy($src, $dst, FILE_EXISTS_REPLACE); } if (!$image = imageapi_image_open($src)) { return FALSE; } if (file_exists($dst)) { watchdog('imagecache', 'Cached image file %dst already exists but is being regenerated. There may be an issue with your rewrite configuration.', array('%dst' => $dst), WATCHDOG_WARNING); } foreach ($actions as $action) { if (!empty($action['data'])) { // Make sure the width and height are computed first so they can be used // in relative x/yoffsets like 'center' or 'bottom'. if (isset($action['data']['width'])) { $action['data']['width'] = _imagecache_percent_filter($action['data']['width'], $image->info['width']); } if (isset($action['data']['height'])) { $action['data']['height'] = _imagecache_percent_filter($action['data']['height'], $image->info['height']); } if (isset($action['data']['xoffset'])) { $action['data']['xoffset'] = _imagecache_keyword_filter($action['data']['xoffset'], $image->info['width'], $action['data']['width']); } if (isset($action['data']['yoffset'])) { $action['data']['yoffset'] = _imagecache_keyword_filter($action['data']['yoffset'], $image->info['height'], $action['data']['height']); } } if (!_imagecache_apply_action($action, $image)) { watchdog('imagecache', 'action(id:%id): %action failed for %src', array('%id' => $action['actionid'], '%action' => $action['action'], '%src' => $src), WATCHDOG_ERROR); return FALSE; } } if (!imageapi_image_close($image, $dst)) { watchdog('imagecache', 'There was an error saving the new image file %dst.', array('%dst' => $dst), WATCHDOG_ERROR); return FALSE; } return TRUE; } /** * Implementation of hook_user(). */ function imagecache_user($op, &$edit, &$account, $category = NULL) { // Flush cached old user picture. if ($op == 'update' && !empty($account->picture)) { imagecache_image_flush($account->picture); } } /** * Implementation of filefield.module's hook_file_delete(). * * Remove derivative images after the originals are deleted by filefield. */ function imagecache_file_delete($file) { imagecache_image_flush($file->filepath); } /** * Implementation of hook_field_formatter_info(). * * imagecache formatters are named as $presetname_$style * $style is used to determine how the preset should be rendered. * If you are implementing custom imagecache formatters please treat _ as * reserved. * * @todo: move the linking functionality up to imagefield and clean up the default image * integration. */ function imagecache_field_formatter_info() { $formatters = array(); foreach (imagecache_presets() as $preset) { $formatters[$preset['presetname'] .'_default'] = array( 'label' => t('@preset image', array('@preset' => $preset['presetname'])), 'field types' => array('image', 'filefield'), ); $formatters[$preset['presetname'] .'_linked'] = array( 'label' => t('@preset image linked to node', array('@preset' => $preset['presetname'])), 'field types' => array('image', 'filefield'), ); $formatters[$preset['presetname'] .'_imagelink'] = array( 'label' => t('@preset image linked to image', array('@preset' => $preset['presetname'])), 'field types' => array('image', 'filefield'), ); $formatters[$preset['presetname'] .'_path'] = array( 'label' => t('@preset file path', array('@preset' => $preset['presetname'])), 'field types' => array('image', 'filefield'), ); $formatters[$preset['presetname'] .'_url'] = array( 'label' => t('@preset URL', array('@preset' => $preset['presetname'])), 'field types' => array('image', 'filefield'), ); } return $formatters; } function theme_imagecache_formatter_default($element) { // Inside a view $element may contain NULL data. In that case, just return. if (empty($element['#item']['fid'])) { return ''; } // Extract the preset name from the formatter name. $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); $style = 'linked'; $style = 'default'; $item = $element['#item']; $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; return theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title'], array('class' => $class)); } function theme_imagecache_formatter_linked($element) { // Inside a view $element may contain NULL data. In that case, just return. if (empty($element['#item']['fid'])) { return ''; } // Extract the preset name from the formatter name. $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); $style = 'linked'; $item = $element['#item']; $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']); $path = empty($item['nid']) ? '' : 'node/'. $item['nid']; $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE)); } function theme_imagecache_formatter_imagelink($element) { // Inside a view $element may contain NULL data. In that case, just return. if (empty($element['#item']['fid'])) { return ''; } // Extract the preset name from the formatter name. $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); $style = 'imagelink'; $item = $element['#item']; $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']); $path = file_create_url($item['filepath']); $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE)); } function theme_imagecache_formatter_path($element) { // Inside a view $element may contain NULL data. In that case, just return. if (empty($element['#item']['fid'])) { return ''; } // Extract the preset name from the formatter name. $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); return imagecache_create_path($presetname, $element['#item']['filepath']); } function theme_imagecache_formatter_url($element) { // Inside a view $element may contain NULL data. In that case, just return. if (empty($element['#item']['fid'])) { return ''; } // Extract the preset name from the formatter name. $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); return imagecache_create_url($presetname, $element['#item']['filepath']); } /** * Accept a percentage and return it in pixels. */ function _imagecache_percent_filter($value, $current_pixels) { if (strpos($value, '%') !== FALSE) { $value = str_replace('%', '', $value) * 0.01 * $current_pixels; } return $value; } /** * Accept a keyword (center, top, left, etc) and return it as an offset in pixels. */ function _imagecache_keyword_filter($value, $current_pixels, $new_pixels) { switch ($value) { case 'top': case 'left': $value = 0; break; case 'bottom': case 'right': $value = $current_pixels - $new_pixels; break; case 'center': $value = $current_pixels/2 - $new_pixels/2; break; } return $value; } /** * Recursively delete all files and folders in the specified filepath, then * delete the containing folder. * * Note that this only deletes visible files with write permission. * * @param string $path * A filepath relative to file_directory_path. */ function _imagecache_recursive_delete($path) { if (is_file($path) || is_link($path)) { unlink($path); } elseif (is_dir($path)) { $d = dir($path); while (($entry = $d->read()) !== FALSE) { if ($entry == '.' || $entry == '..') continue; $entry_path = $path .'/'. $entry; _imagecache_recursive_delete($entry_path); } $d->close(); rmdir($path); } else { watchdog('imagecache', 'Unknown file type(%path) stat: %stat ', array('%path' => $path, '%stat' => print_r(stat($path),1)), WATCHDOG_ERROR); } } /** * Create and image tag for an imagecache derivative * * @param $presetname * String with the name of the preset used to generate the derivative image. * @param $path * String path to the original image you wish to create a derivative image * tag for. * @param $alt * Optional string with alternate text for the img element. * @param $title * Optional string with title for the img element. * @param $attributes * Optional drupal_attributes() array. If $attributes is an array then the * default imagecache classes will not be set automatically, you must do this * manually. * @param $getsize * If set to TRUE, the image's dimension are fetched and added as width/height * attributes. * @param $absolute * A Boolean indicating that the URL should be absolute. Defaults to TRUE. * @return * HTML img element string. */ function theme_imagecache($presetname, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE, $absolute = TRUE) { // Check is_null() so people can intentionally pass an empty array of // to override the defaults completely. if (is_null($attributes)) { $attributes = array('class' => 'imagecache imagecache-'. $presetname); } if ($getsize && ($image = image_get_info(imagecache_create_path($presetname, $path)))) { $attributes['width'] = $image['width']; $attributes['height'] = $image['height']; } $attributes = drupal_attributes($attributes); $imagecache_url = imagecache_create_url($presetname, $path, FALSE, $absolute); return ''. check_plain($alt) .''; } /** * Create a link the original image that wraps the derivative image. * * @param $presetname * String with the name of the preset used to generate the derivative image. * @param $path * String path to the original image you wish to create a derivative image * tag for. * @param $alt * Optional string with alternate text for the img element. * @param $title * Optional string with title for the img element. * @param attributes * Optional drupal_attributes() array for the link. * @return * An HTML string. */ function theme_imagecache_imagelink($presetname, $path, $alt = '', $title = '', $attributes = NULL) { $image = theme('imagecache', $presetname, $path, $alt, $title); $original_image_url = file_create_url($path); return l($image, $original_image_url, array('absolute' => FALSE, 'html' => TRUE, 'attributes' => $attributes)); } /** * ImageCache 2.x API * * The API for imagecache has changed. The 2.x API returns more structured * data, has shorter function names, and implements more aggressive metadata * caching. * */ /** * Get an array of all presets and their settings. * * @param reset * if set to TRUE it will clear the preset cache * * @return * array of presets array( $preset_id => array('presetid' => integer, 'presetname' => string)) */ function imagecache_presets($reset = FALSE) { static $presets = array(); // Clear caches if $reset is TRUE; if ($reset) { $presets = array(); cache_clear_all('imagecache:presets', 'cache'); // Clear the content.module cache (refreshes the list of formatters provided by imagefield.module). if (module_exists('content')) { content_clear_type_cache(); } } // Return presets if the array is populated. if (!empty($presets)) { return $presets; } // Grab from cache or build the array. To ensure that the Drupal 5 upgrade // path works, we also check whether the presets list is an array. if (($cache = cache_get('imagecache:presets', 'cache')) && is_array($cache->data)) { $presets = $cache->data; } else { $normal_presets = array(); $result = db_query('SELECT * FROM {imagecache_preset} ORDER BY presetname'); while ($preset = db_fetch_array($result)) { $presets[$preset['presetid']] = $preset; $presets[$preset['presetid']]['actions'] = imagecache_preset_actions($preset); $presets[$preset['presetid']]['storage'] = IMAGECACHE_STORAGE_NORMAL; // Collect normal preset names so we can skip defaults and mark overrides accordingly $normal_presets[$preset['presetname']] = $preset['presetid']; } // Collect default presets and allow modules to modify them before they // are cached. $default_presets = module_invoke_all('imagecache_default_presets'); drupal_alter('imagecache_default_presets', $default_presets); // Add in default presets if they don't conflict with any normal presets. // Mark normal presets that take the same preset namespace as overrides. foreach ($default_presets as $preset) { if (!empty($preset['presetname'])) { if (!isset($normal_presets[$preset['presetname']])) { $preset['storage'] = IMAGECACHE_STORAGE_DEFAULT; // Use a string preset identifier $preset['presetid'] = $preset['presetname']; $presets[$preset['presetname']] = $preset; } else { $presetid = $normal_presets[$preset['presetname']]; $presets[$presetid]['storage'] = IMAGECACHE_STORAGE_OVERRIDE; } } } cache_set('imagecache:presets', $presets); } return $presets; } /** * Load a preset by preset_id. * * @param preset_id * The numeric id of a preset. * * @return * preset array( 'presetname' => string, 'presetid' => integet) * empty array if preset_id is an invalid preset */ function imagecache_preset($preset_id, $reset = FALSE) { $presets = imagecache_presets($reset); return (isset($presets[$preset_id])) ? $presets[$preset_id] : array(); } /** * Load a preset by name. * * @param preset_name * * @return * preset array( 'presetname' => string, 'presetid' => integer) * empty array if preset_name is an invalid preset */ function imagecache_preset_by_name($preset_name) { static $presets_by_name = array(); if (!$presets_by_name && $presets = imagecache_presets()) { foreach ($presets as $preset) { $presets_by_name[$preset['presetname']] = $preset; } } return (isset($presets_by_name[$preset_name])) ? $presets_by_name[$preset_name] : array(); } /** * Save an ImageCache preset. * * @param preset * an imagecache preset array. * @return * a preset array. In the case of a new preset, 'presetid' will be populated. */ function imagecache_preset_save($preset) { // @todo: CRUD level validation? if (isset($preset['presetid']) && is_numeric($preset['presetid'])) { drupal_write_record('imagecache_preset', $preset, 'presetid'); } else { drupal_write_record('imagecache_preset', $preset); } // Reset presets cache. imagecache_preset_flush($preset); imagecache_presets(TRUE); // Rebuild Theme Registry drupal_rebuild_theme_registry(); return $preset; } function imagecache_preset_delete($preset) { imagecache_preset_flush($preset); db_query('DELETE FROM {imagecache_action} where presetid = %d', $preset['presetid']); db_query('DELETE FROM {imagecache_preset} where presetid = %d', $preset['presetid']); imagecache_presets(TRUE); return TRUE; } function imagecache_preset_actions($preset, $reset = FALSE) { static $actions_cache = array(); if ($reset || empty($actions_cache[$preset['presetid']])) { $result = db_query('SELECT * FROM {imagecache_action} where presetid = %d order by weight', $preset['presetid']); while ($row = db_fetch_array($result)) { $row['data'] = unserialize($row['data']); $actions_cache[$preset['presetid']][] = $row; } } return isset($actions_cache[$preset['presetid']]) ? $actions_cache[$preset['presetid']] : array(); } /** * Flush cached media for a preset. * * @param preset * an imagecache preset array. */ function imagecache_preset_flush($preset) { if (user_access('flush imagecache')) { $presetdir = realpath(file_directory_path() .'/imagecache/'. $preset['presetname']); if (is_dir($presetdir)) { _imagecache_recursive_delete($presetdir); } } } /** * Clear cached versions of a specific file in all presets. * @param $path * The Drupal file path to the original image. */ function imagecache_image_flush($path) { foreach (imagecache_presets() as $preset) { file_delete(imagecache_create_path($preset['presetname'], $path)); } } function imagecache_action($actionid) { static $actions; if (!isset($actions[$actionid])) { $action = array(); $result = db_query('SELECT * FROM {imagecache_action} WHERE actionid=%d', $actionid); if ($row = db_fetch_array($result)) { $action = $row; $action['data'] = unserialize($action['data']); $definition = imagecache_action_definition($action['action']); $action = array_merge($definition, $action); $actions[$actionid] = $action; } } return $actions[$actionid]; } function imagecache_action_load($actionid) { return imagecache_action($actionid, TRUE); } function imagecache_action_save($action) { $definition = imagecache_action_definition($action['action']); $action = array_merge($definition, $action); // Some actions don't have data. Make an empty one to prevent SQL errors. if (!isset($action['data'])) { $action['data'] = array(); } if (!empty($action['actionid'])) { drupal_write_record('imagecache_action', $action, 'actionid'); } else { drupal_write_record('imagecache_action', $action); } $preset = imagecache_preset($action['presetid']); imagecache_preset_flush($preset); imagecache_presets(TRUE); return $action; } function imagecache_action_delete($action) { db_query('DELETE FROM {imagecache_action} WHERE actionid=%d', $action['actionid']); $preset = imagecache_preset($action['presetid']); imagecache_preset_flush($preset); imagecache_presets(TRUE); } /** * Implementation of hook_action_info(). * * Note: These are actions in the Drupal core trigger.module sense, not * ImageCache actions. */ function imagecache_action_info() { $actions = array(); if (module_exists('filefield')) { $actions['imagecache_flush_action'] = array( 'type' => 'node', 'description' => t("ImageCache: Flush ALL presets for this node's filefield images"), 'configurable' => FALSE, 'hooks' => array( 'nodeapi' => array('presave', 'delete', 'insert', 'update'), ) ); $actions['imagecache_generate_all_action'] = array( 'type' => 'node', 'description' => t("ImageCache: Generate ALL presets for this node's filefield images"), 'configurable' => FALSE, 'hooks' => array( 'nodeapi' => array('presave', 'insert', 'update'), ) ); $actions['imagecache_generate_action'] = array( 'type' => 'node', 'description' => t("ImageCache: Generate configured preset(s) for this node's filefield images"), 'configurable' => TRUE, 'hooks' => array( 'nodeapi' => array('presave', 'insert', 'update'), ) ); } return $actions; } /** * Flush all imagecache presets for a given node. * * @param $node * A node object. * @param $context * Contains values from the calling action. * * @see imagecache_action_info() */ function imagecache_flush_action(&$node, $context) { $files = imagecache_get_images_in_node($node); if (!empty($files)) { foreach ($files as $file) { imagecache_image_flush($file['filepath']); } } } /** * Generate all imagecache presets for the given node. * * @param $node * A node object. * @param $context * Contains values from the calling action. * * @see imagecache_action_info() */ function imagecache_generate_all_action(&$node, $context) { $files = imagecache_get_images_in_node($node); $presets = imagecache_presets(); if (!empty($files) && !empty($presets)) { foreach ($files as $file) { foreach ($presets as $presetname) { imagecache_generate_image($presetname['presetname'], $file['filepath']); } } } } /** * Generate imagecache presets for the given node and presets. * * @param $node * A node object. * @param $context * Contains values from the calling action. * * @see imagecache_action_info() * @see imagecache_generate_action_form() */ function imagecache_generate_action(&$node, $context) { $files = imagecache_get_images_in_node($node); if (!empty($files) && !empty($context['imagecache_presets'])) { foreach ($files as $file) { foreach ($context['imagecache_presets'] as $presetname) { imagecache_generate_image($presetname, $file['filepath']); } } } } /** * Form for configuring the generate action. * * @see imagecache_generate_action() */ function imagecache_generate_action_form($context) { $options = array(); foreach (imagecache_presets() as $preset) { $options[$preset['presetname']] = $preset['presetname']; } $form['presets'] = array( '#type' => 'checkboxes', '#options' => $options, '#description' => t('Select which imagecache presets will be effected'), '#required' => TRUE, '#default_value' => isset($context['imagecache_presets']) ? $context['imagecache_presets'] : array(), ); // Filter out false checkboxes: http://drupal.org/node/61760#comment-402631 $form['array_filter'] = array('#type' => 'value', '#value' => TRUE); return $form; } /** * Generate a derivative image given presetname and filepath. * * This is a developer friendly version of _imagecache_cache(), it doesn't worry * about sending HTTP headers or an image back to the client so it's much * simpler. * * @param $presetname * ImageCache preset array. * @param $filepath * String filepath from the files table. * @return * A Boolean indicating if the operation succeeded. */ function imagecache_generate_image($presetname, $filepath) { $preset = imagecache_preset_by_name($presetname); if (empty($preset['presetname'])) { return FALSE; } $destination = imagecache_create_path($preset['presetname'], $filepath); if (file_exists($destination)) { return TRUE; } return imagecache_build_derivative($preset['actions'], $filepath, $destination); } /** * Given a node, get all images associated with it. * * Currently this only works with images stored in filefields. * * @param $node * Node object. * @return * An array of info from the files table. */ function imagecache_get_images_in_node(&$node) { $files = array(); if (module_exists('filefield')) { $data = filefield_get_node_files($node); foreach ($data as $key => $value) { if (stristr($value['filemime'], 'image')) { $files[$key] = $value; } } } return $files; }