6 * Dynamic image resizer and image cacher.
8 * ImageCache allows you to setup presets for image processing.
9 * If an ImageCache derivative doesn't exist the web server's
10 * rewrite rules will pass the request to Drupal which in turn
11 * hands it off to imagecache to dynamically generate the file.
13 * To view a derivative image you request a special url containing
14 * 'imagecache/<presetname>/path/to/file.ext.
16 * Presets can be managed at http://example.com/admin/build/imagecache.
18 * To view a derivative image you request a special url containing
19 * 'imagecache/<presetname>/path/to/file.ext.
21 * If you had a preset names 'thumbnail' and you wanted to see the
22 * thumbnail version of http://example.com/files/path/to/myimage.jpg you
23 * would use http://example.com/files/imagecache/thumbnail/path/to/myimage.jpg
25 * ImageCache provides formatters for CCK Imagefields and is leveraged by several
26 * other modules. ImageCache also relies heavily on ImageAPI for it's image processing.
27 * If there are errors with actual image processing look to ImageAPI first.
29 * @todo: add watermarking capabilities.
34 * Imagecache preset storage constant for user-defined presets in the DB.
36 define('IMAGECACHE_STORAGE_NORMAL', 0);
39 * Imagecache preset storage constant for module-defined presets in code.
41 define('IMAGECACHE_STORAGE_DEFAULT', 1);
44 * Imagecache preset storage constant for user-defined presets that override
45 * module-defined presets.
47 define('IMAGECACHE_STORAGE_OVERRIDE', 2);
49 /*********************************************************************************************
51 *********************************************************************************************/
54 * Implementation of hook_perm().
56 function imagecache_perm() {
57 $perms = array('administer imagecache', 'flush imagecache');
58 foreach (imagecache_presets() as
$preset) {
59 $perms[] = 'view imagecache '.
$preset['presetname'];
65 * Implementation of hook_menu().
67 function imagecache_menu() {
70 // standard imagecache callback.
71 $items[file_directory_path() .
'/imagecache'] = array(
72 'page callback' => 'imagecache_cache',
73 'access callback' => TRUE
,
74 'type' => MENU_CALLBACK
76 // private downloads imagecache callback
77 $items['system/files/imagecache'] = array(
78 'page callback' => 'imagecache_cache_private',
79 'access callback' => TRUE
,
80 'type' => MENU_CALLBACK
87 * Clear imagecache presets cache on admin/build/modules form.
89 function imagecache_form_system_modules_alter(&$form, $form_state) {
90 imagecache_presets(TRUE
);
94 * Implementation of hook_theme().
96 function imagecache_theme() {
98 'imagecache' => array(
105 'imagecache_imagelink' => array(
106 'arguments' => array(
111 'attributes' => array(),
113 'imagecache_resize' => array(
114 'file' => 'imagecache_actions.inc',
115 'arguments' => array('element' => NULL
),
117 'imagecache_scale' => array(
118 'file' => 'imagecache_actions.inc',
119 'arguments' => array('element' => NULL
),
121 'imagecache_scale_and_crop' => array(
122 'file' => 'imagecache_actions.inc',
123 'arguments' => array('element' => NULL
),
125 'imagecache_deprecated_scale' => array(
126 'file' => 'imagecache_actions.inc',
127 'arguments' => array('element' => NULL
),
129 'imagecache_crop' => array(
130 'file' => 'imagecache_actions.inc',
131 'arguments' => array('element' => NULL
),
133 'imagecache_desaturate' => array(
134 'file' => 'imagecache_actions.inc',
135 'arguments' => array('element' => NULL
),
137 'imagecache_rotate' => array(
138 'file' => 'imagecache_actions.inc',
139 'arguments' => array('element' => NULL
),
141 'imagecache_sharpen' => array(
142 'file' => 'imagecache_actions.inc',
143 'arguments' => array('element' => NULL
),
147 foreach (imagecache_presets() as
$preset) {
148 $theme['imagecache_formatter_'.
$preset['presetname'] .
'_default'] = array(
149 'arguments' => array('element' => NULL
),
150 'function' => 'theme_imagecache_formatter_default',
152 $theme['imagecache_formatter_'.
$preset['presetname'] .
'_linked'] = array(
153 'arguments' => array('element' => NULL
),
154 'function' => 'theme_imagecache_formatter_linked',
156 $theme['imagecache_formatter_'.
$preset['presetname'] .
'_imagelink'] = array(
157 'arguments' => array('element' => NULL
),
158 'function' => 'theme_imagecache_formatter_imagelink',
160 $theme['imagecache_formatter_'.
$preset['presetname'] .
'_path'] = array(
161 'arguments' => array('element' => NULL
),
162 'function' => 'theme_imagecache_formatter_path',
164 $theme['imagecache_formatter_'.
$preset['presetname'] .
'_url'] = array(
165 'arguments' => array('element' => NULL
),
166 'function' => 'theme_imagecache_formatter_url',
175 * Implementation of hook_imagecache_actions.
178 * An array of information on the actions implemented by a module. The array
179 * contains a sub-array for each action node type, with the machine-readable
180 * action name as the key. Each sub-array has up to 3 attributes. Possible
183 * "name": the human-readable name of the action. Required.
184 * "description": a brief description of the action. Required.
185 * "file": the name of the include file the action can be found
186 * in relative to the implementing module's path.
188 function imagecache_imagecache_actions() {
190 'imagecache_resize' => array(
192 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.',
193 'file' => 'imagecache_actions.inc',
195 'imagecache_scale' => array(
197 'description' => 'Resize an image maintaining the original aspect-ratio (only one value necessary).',
198 'file' => 'imagecache_actions.inc',
200 'imagecache_deprecated_scale' => array(
201 'name' => 'Deprecated Scale',
202 'description' => 'Precursor to Scale and Crop. Has inside and outside dimension support. This action will be removed in ImageCache 2.1).',
203 'file' => 'imagecache_actions.inc',
205 'imagecache_scale_and_crop' => array(
206 'name' => 'Scale And Crop',
207 'description' => 'Resize an image while maintaining aspect ratio, then crop it to the specified dimensions.',
208 'file' => 'imagecache_actions.inc',
210 'imagecache_crop' => array(
212 'description' => 'Crop an image to the rectangle specified by the given offsets and dimensions.',
213 'file' => 'imagecache_actions.inc',
215 'imagecache_desaturate' => array(
216 'name' => 'Desaturate',
217 'description' => 'Convert an image to grey scale.',
218 'file' => 'imagecache_actions.inc',
220 'imagecache_rotate' => array(
222 'description' => 'Rotate an image.',
223 'file' => 'imagecache_actions.inc',
225 'imagecache_sharpen' => array(
227 'description' => 'Sharpen an image using unsharp masking.',
228 'file' => 'imagecache_actions.inc',
236 * Pull in actions exposed by other modules using hook_imagecache_actions().
239 * Boolean flag indicating whether the cached data should be
240 * wiped and recalculated.
243 * An array of actions to be used when transforming images.
245 function imagecache_action_definitions($reset = FALSE
) {
247 if (!isset($actions) || $reset) {
248 if (!$reset && ($cache = cache_get('imagecache_actions')) && !empty($cache->data
)) {
249 $actions = $cache->data
;
252 foreach (module_implements('imagecache_actions') as
$module) {
253 foreach (module_invoke($module, 'imagecache_actions') as
$key => $action) {
254 $action['module'] = $module;
255 if (!empty($action['file'])) {
256 $action['file'] = drupal_get_path('module', $action['module']) .
'/'.
$action['file'];
258 $actions[$key] = $action;
261 uasort($actions, '_imagecache_definitions_sort');
262 cache_set('imagecache_actions', $actions);
268 function _imagecache_definitions_sort($a, $b) {
274 return ($a < $b) ?
-1 : 1;
277 function imagecache_action_definition($action) {
278 static
$definition_cache;
279 if (!isset($definition_cache[$action])) {
280 $definitions = imagecache_action_definitions();
281 $definition = (isset($definitions[$action])) ?
$definitions[$action] : array();
283 if (isset($definition['file'])) {
284 require_once($definition['file']);
286 $definition_cache[$action] = $definition;
288 return $definition_cache[$action];
292 * Return a URL that points to the location of a derivative of the
293 * original image transformed with the given preset.
295 * Special care is taken to make this work with the possible combinations of
296 * Clean URLs and public/private downloads. For example, when Clean URLs are not
297 * available an URL with query should be returned, like
298 * http://example.com/?q=files/imagecache/foo.jpg, so that imagecache is able
299 * intercept the request for this file.
301 * This code is very similar to the Drupal core function file_create_url(), but
302 * handles the case of Clean URLs and public downloads differently however.
306 * String specifying the path to the image file.
307 * @param $bypass_browser_cache
308 * A Boolean indicating that the URL for the image should be distinct so that
309 * the visitors browser will not be able to use a previously cached version.
312 * A Boolean indicating that the URL should be absolute. Defaults to TRUE.
314 function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = FALSE
, $absolute = TRUE
) {
315 $path = _imagecache_strip_file_directory($filepath);
316 if (module_exists('transliteration')) {
317 $path = transliteration_get($path);
320 $args = array('absolute' => $absolute, 'query' => $bypass_browser_cache ?
time() : $bypass_browser_cache);
321 switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC
)) {
322 case FILE_DOWNLOADS_PUBLIC
:
323 $base = $absolute ?
$GLOBALS['base_url'] .
'/' : '';
324 return url($base .
file_directory_path() .
"/imagecache/$presetname/$path", $args);
325 case FILE_DOWNLOADS_PRIVATE
:
326 return url("system/files/imagecache/$presetname/$path", $args);
331 * Return a file system location that points to the location of a derivative
332 * of the original image at @p $path, transformed with the given @p $preset.
333 * Keep in mind that the image might not yet exist and won't be created.
335 function imagecache_create_path($presetname, $path) {
336 $path = _imagecache_strip_file_directory($path);
337 return file_create_path() .
'/imagecache/'.
$presetname .
'/'.
$path;
341 * Remove a possible leading file directory path from the given path.
343 function _imagecache_strip_file_directory($path) {
344 $dirpath = file_directory_path();
345 $dirlen = strlen($dirpath);
346 if (substr($path, 0, $dirlen + 1) == $dirpath .
'/') {
347 $path = substr($path, $dirlen + 1);
354 * callback for handling public files imagecache requests.
356 function imagecache_cache() {
357 $args = func_get_args();
358 $preset = check_plain(array_shift($args));
359 $path = implode('/', $args);
360 _imagecache_cache($preset, $path);
364 * callback for handling private files imagecache requests
366 function imagecache_cache_private() {
367 $args = func_get_args();
368 $preset = check_plain(array_shift($args));
369 $source = implode('/', $args);
371 if (user_access('view imagecache '.
$preset)) {
372 _imagecache_cache($preset, $source);
375 // if there is a 403 image, display it.
376 $accesspath = file_create_path('imagecache/'.
$preset .
'.403.png');
377 if (is_file($accesspath)) {
378 imagecache_transfer($accesspath);
381 header('HTTP/1.0 403 Forbidden');
387 * handle request validation and responses to imagecache requests.
389 function _imagecache_cache($presetname, $path) {
390 if (!$preset = imagecache_preset_by_name($presetname)) {
391 // Send a 404 if we don't know of a preset.
392 header("HTTP/1.0 404 Not Found");
396 // umm yeah deliver it early if it is there. especially useful
397 // to prevent lock files from being created when delivering private files.
398 $dst = imagecache_create_path($preset['presetname'], $path);
400 imagecache_transfer($dst);
403 // preserve path for watchdog.
406 // Check if the path to the file exists.
407 if (!is_file($src) && !is_file($src = file_create_path($src))) {
408 watchdog('imagecache', '404: Unable to find %image ', array('%image' => $src), WATCHDOG_ERROR
);
409 header("HTTP/1.0 404 Not Found");
413 // Bail if the requested file isn't an image you can't request .php files
415 if (!getimagesize($src)) {
416 watchdog('imagecache', '403: File is not an image %image ', array('%image' => $src), WATCHDOG_ERROR
);
417 header('HTTP/1.0 403 Forbidden');
421 $lockfile = file_directory_temp() .
'/'.
$preset['presetname'] .
basename($src);
422 if (file_exists($lockfile)) {
423 watchdog('imagecache', 'ImageCache already generating: %dst, Lock file: %tmp.', array('%dst' => $dst, '%tmp' => $lockfile), WATCHDOG_NOTICE
);
424 // 307 Temporary Redirect, to myself. Lets hope the image is done next time around.
425 header('Location: '.
request_uri(), TRUE
, 307);
429 // register the shtdown function to clean up lock files. by the time shutdown
430 // functions are being called the cwd has changed from document root, to
431 // server root so absolute paths must be used for files in shutdown functions.
432 register_shutdown_function('file_delete', realpath($lockfile));
434 // check if deriv exists... (file was created between apaches request handler and reaching this code)
435 // otherwise try to create the derivative.
436 if (file_exists($dst) || imagecache_build_derivative($preset['actions'], $src, $dst)) {
437 imagecache_transfer($dst);
439 // Generate an error if image could not generate.
440 watchdog('imagecache', 'Failed generating an image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset['presetname']), WATCHDOG_ERROR
);
441 header("HTTP/1.0 500 Internal Server Error");
446 * Apply an action to an image.
453 * Boolean, TRUE indicating success and FALSE failure.
455 function _imagecache_apply_action($action, $image) {
456 $actions = imagecache_action_definitions();
458 if ($definition = imagecache_action_definition($action['action'])) {
459 $function = $action['action'] .
'_image';
460 if (function_exists($function)) {
461 return $function($image, $action['data']);
464 // skip undefined actions.. module probably got uninstalled or disabled.
465 watchdog('imagecache', 'non-existant action %action', array('%action' => $action['action']), WATCHDOG_NOTICE
);
470 * Helper function to transfer files from imagecache.
472 * Determines MIME type and sets a last modified header.
475 * String containing the path to file to be transferred.
477 * This function does not return. It calls exit().
480 function imagecache_transfer($path) {
481 $size = getimagesize($path);
482 $headers = array('Content-Type: '.
mime_header_encode($size['mime']));
484 if ($fileinfo = stat($path)) {
485 $headers[] = 'Content-Length: '.
$fileinfo[7];
486 _imagecache_cache_set_cache_headers($fileinfo, $headers);
488 file_transfer($path, $headers);
493 * Set file headers that handle "If-Modified-Since" correctly for the
496 * Note that this function may return or may call exit().
498 * Most code has been taken from drupal_page_cache_header().
501 * Array returned by stat().
503 * Array of existing headers.
505 * Nothing but beware that this function may not return.
507 function _imagecache_cache_set_cache_headers($fileinfo, &$headers) {
508 // Set default values:
509 $last_modified = gmdate('D, d M Y H:i:s', $fileinfo[9]) .
' GMT';
510 $etag = '"'.
md5($last_modified) .
'"';
512 // See if the client has provided the required HTTP headers:
513 $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
514 ?
stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
516 $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
517 ?
stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
520 if ($if_modified_since && $if_none_match
521 && $if_none_match == $etag // etag must match
522 && $if_modified_since == $last_modified) { // if-modified-since must match
523 header('HTTP/1.1 304 Not Modified');
524 // All 304 responses must send an etag if the 200 response
525 // for the same object contained an etag
526 header('Etag: '.
$etag);
527 // We must also set Last-Modified again, so that we overwrite Drupal's
528 // default Last-Modified header with the right one
529 header('Last-Modified: '.
$last_modified);
533 // Send appropriate response:
534 $headers[] = 'Last-Modified: '.
$last_modified;
535 $headers[] = 'ETag: '.
$etag;
539 * Create a new image based on an image preset.
542 * An image preset array.
544 * Path of the source file.
545 * @param $destination
546 * Path of the destination file.
548 * TRUE if an image derivative is generated, FALSE if no image
549 * derivative is generated. NULL if the derivative is being generated.
551 function imagecache_build_derivative($actions, $src, $dst) {
552 // get the folder for the final location of this preset...
553 $dir = dirname($dst);
555 // Build the destination folder tree if it doesn't already exists.
556 if (!file_check_directory($dir, FILE_CREATE_DIRECTORY
) && !mkdir($dir, 0775, TRUE
)) {
557 watchdog('imagecache', 'Failed to create imagecache directory: %dir', array('%dir' => $dir), WATCHDOG_ERROR
);
561 // Simply copy the file if there are no actions.
562 if (empty($actions)) {
563 return file_copy($src, $dst, FILE_EXISTS_REPLACE
);
566 if (!$image = imageapi_image_open($src)) {
570 if (file_exists($dst)) {
571 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
);
574 foreach ($actions as
$action) {
575 if (!empty($action['data'])) {
576 // Make sure the width and height are computed first so they can be used
577 // in relative x/yoffsets like 'center' or 'bottom'.
578 if (isset($action['data']['width'])) {
579 $action['data']['width'] = _imagecache_percent_filter($action['data']['width'], $image->info
['width']);
581 if (isset($action['data']['height'])) {
582 $action['data']['height'] = _imagecache_percent_filter($action['data']['height'], $image->info
['height']);
584 if (isset($action['data']['xoffset'])) {
585 $action['data']['xoffset'] = _imagecache_keyword_filter($action['data']['xoffset'], $image->info
['width'], $action['data']['width']);
587 if (isset($action['data']['yoffset'])) {
588 $action['data']['yoffset'] = _imagecache_keyword_filter($action['data']['yoffset'], $image->info
['height'], $action['data']['height']);
591 if (!_imagecache_apply_action($action, $image)) {
592 watchdog('imagecache', 'action(id:%id): %action failed for %src', array('%id' => $action['actionid'], '%action' => $action['action'], '%src' => $src), WATCHDOG_ERROR
);
597 if (!imageapi_image_close($image, $dst)) {
598 watchdog('imagecache', 'There was an error saving the new image file %dst.', array('%dst' => $dst), WATCHDOG_ERROR
);
606 * Implementation of hook_user().
608 function imagecache_user($op, &$edit, &$account, $category = NULL
) {
609 // Flush cached old user picture.
610 if ($op == 'update' && !empty($account->picture
)) {
611 imagecache_image_flush($account->picture
);
616 * Implementation of filefield.module's hook_file_delete().
618 * Remove derivative images after the originals are deleted by filefield.
620 function imagecache_file_delete($file) {
621 imagecache_image_flush($file->filepath
);
625 * Implementation of hook_field_formatter_info().
627 * imagecache formatters are named as $presetname_$style
628 * $style is used to determine how the preset should be rendered.
629 * If you are implementing custom imagecache formatters please treat _ as
632 * @todo: move the linking functionality up to imagefield and clean up the default image
635 function imagecache_field_formatter_info() {
636 $formatters = array();
637 foreach (imagecache_presets() as
$preset) {
638 $formatters[$preset['presetname'] .
'_default'] = array(
639 'label' => t('@preset image', array('@preset' => $preset['presetname'])),
640 'field types' => array('image', 'filefield'),
642 $formatters[$preset['presetname'] .
'_linked'] = array(
643 'label' => t('@preset image linked to node', array('@preset' => $preset['presetname'])),
644 'field types' => array('image', 'filefield'),
646 $formatters[$preset['presetname'] .
'_imagelink'] = array(
647 'label' => t('@preset image linked to image', array('@preset' => $preset['presetname'])),
648 'field types' => array('image', 'filefield'),
650 $formatters[$preset['presetname'] .
'_path'] = array(
651 'label' => t('@preset file path', array('@preset' => $preset['presetname'])),
652 'field types' => array('image', 'filefield'),
654 $formatters[$preset['presetname'] .
'_url'] = array(
655 'label' => t('@preset URL', array('@preset' => $preset['presetname'])),
656 'field types' => array('image', 'filefield'),
662 function theme_imagecache_formatter_default($element) {
663 // Inside a view $element may contain NULL data. In that case, just return.
664 if (empty($element['#item']['fid'])) {
668 // Extract the preset name from the formatter name.
669 $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_'));
673 $item = $element['#item'];
674 $item['data']['alt'] = isset($item['data']['alt']) ?
$item['data']['alt'] : '';
675 $item['data']['title'] = isset($item['data']['title']) ?
$item['data']['title'] : NULL
;
677 $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}";
678 return theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title'], array('class' => $class));
681 function theme_imagecache_formatter_linked($element) {
682 // Inside a view $element may contain NULL data. In that case, just return.
683 if (empty($element['#item']['fid'])) {
687 // Extract the preset name from the formatter name.
688 $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_'));
691 $item = $element['#item'];
692 $item['data']['alt'] = isset($item['data']['alt']) ?
$item['data']['alt'] : '';
693 $item['data']['title'] = isset($item['data']['title']) ?
$item['data']['title'] : NULL
;
695 $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']);
696 $path = empty($item['nid']) ?
'' : 'node/'.
$item['nid'];
697 $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}";
698 return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE
));
701 function theme_imagecache_formatter_imagelink($element) {
702 // Inside a view $element may contain NULL data. In that case, just return.
703 if (empty($element['#item']['fid'])) {
707 // Extract the preset name from the formatter name.
708 $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_'));
709 $style = 'imagelink';
711 $item = $element['#item'];
712 $item['data']['alt'] = isset($item['data']['alt']) ?
$item['data']['alt'] : '';
713 $item['data']['title'] = isset($item['data']['title']) ?
$item['data']['title'] : NULL
;
715 $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']);
716 $path = file_create_url($item['filepath']);
717 $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}";
718 return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE
));
721 function theme_imagecache_formatter_path($element) {
722 // Inside a view $element may contain NULL data. In that case, just return.
723 if (empty($element['#item']['fid'])) {
727 // Extract the preset name from the formatter name.
728 $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_'));
730 return imagecache_create_path($presetname, $element['#item']['filepath']);
733 function theme_imagecache_formatter_url($element) {
734 // Inside a view $element may contain NULL data. In that case, just return.
735 if (empty($element['#item']['fid'])) {
739 // Extract the preset name from the formatter name.
740 $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_'));
742 return imagecache_create_url($presetname, $element['#item']['filepath']);
746 * Accept a percentage and return it in pixels.
748 function _imagecache_percent_filter($value, $current_pixels) {
749 if (strpos($value, '%') !== FALSE
) {
750 $value = str_replace('%', '', $value) * 0.01 * $current_pixels;
756 * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
758 function _imagecache_keyword_filter($value, $current_pixels, $new_pixels) {
766 $value = $current_pixels - $new_pixels;
769 $value = $current_pixels/2 - $new_pixels/2;
776 * Recursively delete all files and folders in the specified filepath, then
777 * delete the containing folder.
779 * Note that this only deletes visible files with write permission.
781 * @param string $path
782 * A filepath relative to file_directory_path.
784 function _imagecache_recursive_delete($path) {
785 if (is_file($path) || is_link($path)) {
788 elseif (is_dir($path)) {
790 while (($entry = $d->read()) !== FALSE
) {
791 if ($entry == '.' || $entry == '..') continue;
792 $entry_path = $path .
'/'.
$entry;
793 _imagecache_recursive_delete($entry_path);
799 watchdog('imagecache', 'Unknown file type(%path) stat: %stat ',
800 array('%path' => $path, '%stat' => print_r(stat($path),1)), WATCHDOG_ERROR
);
806 * Create and image tag for an imagecache derivative
809 * String with the name of the preset used to generate the derivative image.
811 * String path to the original image you wish to create a derivative image
814 * Optional string with alternate text for the img element.
816 * Optional string with title for the img element.
818 * Optional drupal_attributes() array. If $attributes is an array then the
819 * default imagecache classes will not be set automatically, you must do this
822 * If set to TRUE, the image's dimension are fetched and added as width/height
825 * A Boolean indicating that the URL should be absolute. Defaults to TRUE.
827 * HTML img element string.
829 function theme_imagecache($presetname, $path, $alt = '', $title = '', $attributes = NULL
, $getsize = TRUE
, $absolute = TRUE
) {
830 // Check is_null() so people can intentionally pass an empty array of
831 // to override the defaults completely.
832 if (is_null($attributes)) {
833 $attributes = array('class' => 'imagecache imagecache-'.
$presetname);
835 if ($getsize && ($image = image_get_info(imagecache_create_path($presetname, $path)))) {
836 $attributes['width'] = $image['width'];
837 $attributes['height'] = $image['height'];
840 $attributes = drupal_attributes($attributes);
841 $imagecache_url = imagecache_create_url($presetname, $path, FALSE
, $absolute);
842 return '<img src="'.
$imagecache_url .
'" alt="'.
check_plain($alt) .
'" title="'.
check_plain($title) .
'" '.
$attributes .
' />';
846 * Create a link the original image that wraps the derivative image.
849 * String with the name of the preset used to generate the derivative image.
851 * String path to the original image you wish to create a derivative image
854 * Optional string with alternate text for the img element.
856 * Optional string with title for the img element.
858 * Optional drupal_attributes() array for the link.
862 function theme_imagecache_imagelink($presetname, $path, $alt = '', $title = '', $attributes = NULL
) {
863 $image = theme('imagecache', $presetname, $path, $alt, $title);
864 $original_image_url = file_create_url($path);
865 return l($image, $original_image_url, array('absolute' => FALSE
, 'html' => TRUE
));
871 * The API for imagecache has changed. The 2.x API returns more structured
872 * data, has shorter function names, and implements more aggressive metadata
878 * Get an array of all presets and their settings.
881 * if set to TRUE it will clear the preset cache
884 * array of presets array( $preset_id => array('presetid' => integer, 'presetname' => string))
886 function imagecache_presets($reset = FALSE
) {
887 static
$presets = array();
889 // Clear caches if $reset is TRUE;
892 cache_clear_all('imagecache:presets', 'cache');
894 // Clear the content.module cache (refreshes the list of formatters provided by imagefield.module).
895 if (module_exists('content')) {
896 content_clear_type_cache();
899 // Return presets if the array is populated.
900 if (!empty($presets)) {
904 // Grab from cache or build the array. To ensure that the Drupal 5 upgrade
905 // path works, we also check whether the presets list is an array.
906 if (($cache = cache_get('imagecache:presets', 'cache')) && is_array($cache->data
)) {
907 $presets = $cache->data
;
910 $normal_presets = array();
912 $result = db_query('SELECT * FROM {imagecache_preset} ORDER BY presetname');
913 while ($preset = db_fetch_array($result)) {
914 $presets[$preset['presetid']] = $preset;
915 $presets[$preset['presetid']]['actions'] = imagecache_preset_actions($preset);
916 $presets[$preset['presetid']]['storage'] = IMAGECACHE_STORAGE_NORMAL
;
918 // Collect normal preset names so we can skip defaults and mark overrides accordingly
919 $normal_presets[$preset['presetname']] = $preset['presetid'];
922 // Collect default presets and allow modules to modify them before they
924 $default_presets = module_invoke_all('imagecache_default_presets');
925 drupal_alter('imagecache_default_presets', $default_presets);
927 // Add in default presets if they don't conflict with any normal presets.
928 // Mark normal presets that take the same preset namespace as overrides.
929 foreach ($default_presets as
$preset) {
930 if (!empty($preset['presetname'])) {
931 if (!isset($normal_presets[$preset['presetname']])) {
932 $preset['storage'] = IMAGECACHE_STORAGE_DEFAULT
;
933 // Use a string preset identifier
934 $preset['presetid'] = $preset['presetname'];
935 $presets[$preset['presetname']] = $preset;
938 $presetid = $normal_presets[$preset['presetname']];
939 $presets[$presetid]['storage'] = IMAGECACHE_STORAGE_OVERRIDE
;
944 cache_set('imagecache:presets', $presets);
950 * Load a preset by preset_id.
953 * The numeric id of a preset.
956 * preset array( 'presetname' => string, 'presetid' => integet)
957 * empty array if preset_id is an invalid preset
959 function imagecache_preset($preset_id, $reset = FALSE
) {
960 $presets = imagecache_presets($reset);
961 return (isset($presets[$preset_id])) ?
$presets[$preset_id] : array();
965 * Load a preset by name.
970 * preset array( 'presetname' => string, 'presetid' => integer)
971 * empty array if preset_name is an invalid preset
974 function imagecache_preset_by_name($preset_name) {
975 static
$presets_by_name = array();
976 if (!$presets_by_name && $presets = imagecache_presets()) {
977 foreach ($presets as
$preset) {
978 $presets_by_name[$preset['presetname']] = $preset;
981 return (isset($presets_by_name[$preset_name])) ?
$presets_by_name[$preset_name] : array();
985 * Save an ImageCache preset.
988 * an imagecache preset array.
990 * a preset array. In the case of a new preset, 'presetid' will be populated.
992 function imagecache_preset_save($preset) {
993 // @todo: CRUD level validation?
994 if (isset($preset['presetid']) && is_numeric($preset['presetid'])) {
995 drupal_write_record('imagecache_preset', $preset, 'presetid');
998 drupal_write_record('imagecache_preset', $preset);
1001 // Reset presets cache.
1002 imagecache_preset_flush($preset);
1003 imagecache_presets(TRUE
);
1005 // Rebuild Theme Registry
1006 drupal_rebuild_theme_registry();
1011 function imagecache_preset_delete($preset) {
1012 imagecache_preset_flush($preset['presetid']);
1013 db_query('DELETE FROM {imagecache_action} where presetid = %d', $preset['presetid']);
1014 db_query('DELETE FROM {imagecache_preset} where presetid = %d', $preset['presetid']);
1015 imagecache_presets(TRUE
);
1019 function imagecache_preset_actions($preset, $reset = FALSE
) {
1020 static
$actions_cache = array();
1022 if ($reset || empty($actions_cache[$preset['presetid']])) {
1023 $result = db_query('SELECT * FROM {imagecache_action} where presetid = %d order by weight', $preset['presetid']);
1024 while ($row = db_fetch_array($result)) {
1025 $row['data'] = unserialize($row['data']);
1026 $actions_cache[$preset['presetid']][] = $row;
1030 return isset($actions_cache[$preset['presetid']]) ?
$actions_cache[$preset['presetid']] : array();
1034 * Flush cached media for a preset.
1038 function imagecache_preset_flush($preset) {
1039 if (user_access('flush imagecache')) {
1040 $presetdir = realpath(file_directory_path() .
'/imagecache/'.
$preset['presetname']);
1041 if (is_dir($presetdir)) {
1042 _imagecache_recursive_delete($presetdir);
1048 * Clear cached versions of a specific file in all presets.
1050 * The Drupal file path to the original image.
1052 function imagecache_image_flush($path) {
1053 foreach (imagecache_presets() as
$preset) {
1054 file_delete(imagecache_create_path($preset['presetname'], $path));
1058 function imagecache_action($actionid) {
1061 if (!isset($actions[$actionid])) {
1064 $result = db_query('SELECT * FROM {imagecache_action} WHERE actionid=%d', $actionid);
1065 if ($row = db_fetch_array($result)) {
1067 $action['data'] = unserialize($action['data']);
1069 $definition = imagecache_action_definition($action['action']);
1070 $action = array_merge($definition, $action);
1071 $actions[$actionid] = $action;
1074 return $actions[$actionid];
1077 function imagecache_action_load($actionid) {
1078 return imagecache_action($actionid, TRUE
);
1081 function imagecache_action_save($action) {
1082 $definition = imagecache_action_definition($action['action']);
1083 $action = array_merge($definition, $action);
1085 if (!empty($action['actionid'])) {
1086 drupal_write_record('imagecache_action', $action, 'actionid');
1089 drupal_write_record('imagecache_action', $action);
1091 $preset = imagecache_preset($action['presetid']);
1092 imagecache_preset_flush($preset);
1093 imagecache_presets(TRUE
);
1097 function imagecache_action_delete($action) {
1098 db_query('DELETE FROM {imagecache_action} WHERE actionid=%d', $action['actionid']);
1099 $preset = imagecache_preset($action['presetid']);
1100 imagecache_preset_flush($preset);
1101 imagecache_presets(TRUE
);