*** empty log message ***
[project/imagecache.git] / imagecache.module
CommitLineData
65d53f72 1<?php
e0cb144b 2// $Id$
65d53f72 3
1c784f3b
DP
4/**
5 * @file
6 * Dynamic image resizer and image cacher.
7 *
8 * Imagecache allows you to setup specialized identifiers for dynamic image
9 * processing and caching. It abuses the rewrite rules used for drupal's clean
10 * URLs to provide dynamic image generation.
11 *
12 * Earlier security flaws were overcome by using 'presets' similar to
13 * image.module. We don't fuss with the database, and creating data that we have
14 * to keep up within Drupal. We do it on the fly and flush it when we want to,
15 * or when a preset changes.
16 *
17 * URLs are of the form: files/imagecache/<preset>/path
18 *
19 * @todo: improve image resizing, reduce distortion.
20 * @todo: add watermarking capabilities.
21 * @todo: split action/handlers into their own little .inc files.
22 * @todo: enforce permissions.
23 *
24 * Notes:
25 * To add a new handler,
26 * add fields and select option to _imagecache_actions_form;
27 * add handling code to imagecache_cache
28 *
29 */
65d53f72 30
42cc9535
DP
31
32/*********************************************************************************************
33 * Drupal Hooks
34 *********************************************************************************************/
35
a09d5c25 36
44eb6ee1
DP
37function imagecache_shutdown_clear_lockfile($path) {
38 if (file_exists($path)) {
39 unlink($path);
40 }
a09d5c25
DP
41}
42
43
1c784f3b
DP
44/**
45 * Implementation of hook_perm().
46 */
e0cb144b 47function imagecache_perm() {
42669fda 48 $perms = array('administer imagecache', 'flush imagecache');
42cc9535
DP
49 foreach(imagecache_presets() as $preset) {
50 $perms[] = 'view imagecache '. $preset['presetname'];
42669fda
DP
51 }
52 return $perms;
e0cb144b
DP
53}
54
1c784f3b
DP
55/**
56 * Implementation of hook_menu().
57 */
65d53f72
DP
58function imagecache_menu($may_cache) {
59 $items = array();
60 if ($may_cache) {
42669fda
DP
61
62 // standard imagecache callback.
1c784f3b
DP
63 $items[] = array(
64 'path' => file_directory_path() .'/imagecache',
65 'callback' => 'imagecache_cache',
66 'access' => TRUE,
67 'type' => MENU_CALLBACK
fa508ec3 68 );
42669fda
DP
69
70 // private downloads imagecache callback
71 $items[] = array(
72 'path' => 'system/files/imagecache',
73 'callback' => 'imagecache_cache_private',
74 'access' => TRUE,
75 'type' => MENU_CALLBACK
76 );
65d53f72
DP
77 }
78 return $items;
79}
80
42cc9535 81
1c784f3b
DP
82/**
83 * Implementation of hook_requirements().
84 */
85function imagecache_requirements($phase) {
86 $requirements = array();
87 // Ensure translations don't break at install time.
88 $t = get_t();
89
90 if ($phase == 'runtime') {
1c784f3b 91 // Check for an image library.
69283245
DP
92 if (count(imageapi_get_available_toolkits()) == 0) {
93 $requirements['imageapi_toolkits'] = array(
1c784f3b
DP
94 'title' => $t('Image Toolkit'),
95 'value' => $t('No image toolkits available'),
96 'severity' => REQUIREMENT_ERROR,
97 'description' => $t('Imagecache requires an imagetoolkit such as <a href="http://php.net/gd">GD2</a> or <a href="http://www.imagemagick.org">Imagemagick</a> be installed on your server.'),
650d9de9 98 );
1c784f3b 99 }
1c784f3b
DP
100 // Check for JPEG/PNG/GIF support.
101 if ('gd' == image_get_toolkit()) {
102 foreach (array('gif', 'jpeg', 'png') as $format) {
103 if (!function_exists('imagecreatefrom'. $format)) {
104 $requirements['gd_'. $format] = array(
105 'title' => $t('GD !format Support', array('!format' => drupal_ucfirst($format))),
106 'value' => $t('Not installed'),
107 'severity' => REQUIREMENT_INFO,
108 'description' => $t('PHP was not compiled with %format support. Imagecache will not be able to process %format images.', array('%format' => $format)),
109 );
110 }
111 }
112 }
113 }
1c784f3b
DP
114 return $requirements;
115}
65d53f72 116
b7c5115b 117/**
650d9de9
DP
118 * Implementation of hook_imagecache_actions.
119 *
120 * @return array
121 * An array of information on the actions implemented by a module. The array contains a
122 * sub-array for each action node type, with the machine-readable action name as the key.
123 * Each sub-array has up to 3 attributes. Possible attributes:
124 *
125 * "name": the human-readable name of the action. Required.
126 * "description": a brief description of the action. Required.
127 * "file": the name of the include file the action can be found
128 * in relative to the implementing module's path.
129 */
130function imagecache_imagecache_actions() {
131 $actions = array(
132 'imagecache_resize' => array(
133 'name' => 'Resize',
134 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.',
135 ),
136 'imagecache_scale' => array(
137 'name' => 'Scale',
138 'description' => 'Resize an image maintaining the original aspect-ratio (only one value necessary).',
139 'file' => 'imagecache_actions.inc',
140 ),
141 'imagecache_deprecated_scale' => array(
142 'name' => 'Deprecated Scale',
143 'description' => 'Precursor to Scale and Crop. Has inside and outside dimension support. This action will be removed in Imagecache 2.1).',
144 'file' => 'imagecache_actions.inc',
145 ),
146 'imagecache_scale_and_crop' => array(
147 'name' => 'Scale And Crop',
148 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.',
149 'file' => 'imagecache_actions.inc',
150 ),
151 'imagecache_crop' => array(
152 'name' => 'Crop',
153 'description' => 'Crop an image to the rectangle specified by the given offsets and dimensions.',
154 'file' => 'imagecache_actions.inc',
155 ),
f3ede8cc
DP
156 'imagecache_desaturate' => array(
157 'name' => 'Desaturate',
158 'description' => 'Convert an image to grey scale.',
159 'file' => 'imagecache_actions.inc',
160 ),
650d9de9
DP
161 );
162
650d9de9
DP
163 return $actions;
164}
165
166/**
167 * Pull in actions exposed by other modules using hook_imagecache_actions().
168 *
169 * @param $reset
170 * Boolean flag indicating whether the cached data should be
171 * wiped and recalculated.
172 *
173 * @return
174 * An array of actions to be used when transforming images.
175 */
176function imagecache_action_definitions($reset = FALSE) {
177 static $actions;
178 if (!isset($actions) || $reset) {
179 if (!$reset && ($cache = cache_get('imagecache_actions')) && !empty($cache->data)) {
180 $actions = unserialize($cache->data);
181 }
182 else {
183 foreach(module_implements('imagecache_actions') as $module) {
184 foreach (module_invoke($module, 'imagecache_actions') as $key => $action) {
185 $action['module'] = $module;
186 if ($action['file']) {
187 $action['file'] = drupal_get_path('module', $action['module']) .'/'. $action['file'];
188 }
189 $actions[$key] = $action;
190 };
191 }
42cc9535 192 uasort($actions, '_imagecache_definitions_sort');
650d9de9
DP
193 cache_set('imagecache_actions', 'cache', serialize($actions));
194 }
195 }
196 return $actions;
197}
198
42cc9535
DP
199function _imagecache_definitions_sort($a, $b) {
200 $a = $a['name'];
201 $b = $b['name'];
202 if ($a == $b) {
203 return 0;
204 }
205 return ($a < $b) ? -1 : 1;
206}
1ae0e73d
DP
207
208function imagecache_action_definition($action) {
209 static $definition_cache;
210 if (!isset($definition_cache[$action])) {
211 $definitions = imagecache_action_definitions();
212 $definition = (isset($definitions[$action])) ? $definitions[$action] : array();
213
214 if ($definition && $definition['file']) {
215 require_once($definition['file']);
216 }
217 $definition_cache[$action] = $definition;
218 }
219 return $definition_cache[$action];
220}
650d9de9
DP
221
222/**
b7c5115b
DP
223 * Return a URL that points to the location of a derivative of the
224 * original image at @p $path, transformed with the given @p $preset.
225 */
42cc9535 226function imagecache_create_url($presetname, $path) {
b7c5115b 227 $path = _imagecache_strip_file_directory($path);
42cc9535 228 return file_create_url(file_directory_path() .'/imagecache/'. $presetname .'/'. $path);
b7c5115b
DP
229}
230
231/**
232 * Return a file system location that points to the location of a derivative
233 * of the original image at @p $path, transformed with the given @p $preset.
234 * Keep in mind that the image might not yet exist and won't be created.
235 */
42cc9535 236function imagecache_create_path($presetname, $path) {
b7c5115b 237 $path = _imagecache_strip_file_directory($path);
42cc9535 238 return file_create_path() .'/imagecache/'. $presetname .'/'. $path;
b7c5115b
DP
239}
240
241/**
242 * Remove a possible leading file directory path from the given path.
243 */
244function _imagecache_strip_file_directory($path) {
245 $dirpath = file_directory_path();
246 $dirlen = strlen($dirpath);
247 if (substr($path, 0, $dirlen + 1) == $dirpath .'/') {
248 $path = substr($path, $dirlen + 1);
249 }
250 return $path;
251}
252
253
42669fda
DP
254/**
255 * callback for handling public files imagecache requests.
256 */
65d53f72 257function imagecache_cache() {
65d53f72 258 $args = func_get_args();
42669fda
DP
259 $preset = check_plain(array_shift($args));
260 $path = implode('/', $args);
261 _imagecache_cache($preset, $path);
262}
277b7f55 263
42669fda
DP
264/**
265 * callback for handling private files imagecache requests
266 */
267function imagecache_cache_private() {
268 $args = func_get_args();
269 $preset = check_plain(array_shift($args));
270 $source = implode('/', $args);
271
272 if (user_access('view imagecache '. $preset)) {
273 _imagecache_cache($preset, $source);
274 }
275 else {
6491b9c4 276 // if there is a 403 image, display it.
42669fda
DP
277 $accesspath = file_create_path('imagecache/'. $preset .'.403.png');
278 if (file_exists($accesspath)) {
279 imagecache_transfer($accesspath);
280 exit;
281 }
282 header('HTTP/1.0 403 Forbidden');
82a7e4a6
DP
283 exit;
284 }
42669fda
DP
285}
286
287/**
288 * handle request validation and responses to imagecache requests.
289 */
42cc9535
DP
290function _imagecache_cache($presetname, $path) {
291 if (!$preset = imagecache_preset_by_name($presetname)) {
42669fda
DP
292 // send a 404 if we dont' know of a preset.
293 header("HTTP/1.0 404 Not Found");
294 exit;
295 }
296 $src = file_create_path($path);
297 if (!is_file($src)) {
6491b9c4
DP
298 // scan the session
299 foreach ($_SESSION['imagefield'] as $fieldname => $files) {
300 foreach ($files as $delta => $file) {
301 if ($file['preview'] == $src) {
302 $dst = tempnam(file_directory_temp(), 'imagecache.preview');
303 if (!imagecache_build_derivative($preset['actions'], $file['filepath'], $dst)) {
304 // Generate an error if image could not generate.
305 watchdog('imagecache', t('Failed generating a preview image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset)), WATCHDOG_ERROR);
306 header("HTTP/1.0 500 Internal Server Error");
307 exit;
308 }
309 imagecache_transfer($dst);
310 unlink($dst);
311 }
312 }
313 }
314
315
316
42669fda 317 // if there is a 404 image uploaded for the preset display it.
42cc9535 318 $notfoundpath = file_create_path('imagecache/'. $preset['presetname'] .'.404.png');
42669fda
DP
319 if (file_exists($notfoundpath)) {
320 imagecache_transfer($notfoundpath);
321 exit;
322 }
323 // otherwise send a 404.
324 header("HTTP/1.0 404 Not Found");
325 exit;
3edf6c97
DP
326 }
327
42cc9535 328 $dst = imagecache_create_path($preset['presetname'], $path);
44eb6ee1
DP
329 $lockfile = file_directory_temp() .'/'. $preset['presetname'] . basename($src) ;
330 if (file_exists($lockfile)) {
331 watchdog('imagecache', t('Imagecache already generating: %dst, Lock file: %tmp.', array('%dst' => $dst, '%tmp' => $lockfile)), WATCHDOG_NOTICE);
42669fda 332 // send a response code that will make the browser wait and reload in a 1/2 sec.
650d9de9 333 // header()
42669fda
DP
334 exit;
335 }
44eb6ee1
DP
336 touch($lockfile);
337 // register the shtdown function to clean up lock files.
338 register_shutdown_function('imagecache_shutdown_clear_lockfile', $lockfile);
339
42669fda
DP
340 // check if deriv exists... (file was created between apaches request handler and reaching this code)
341 // otherwise try to create the derivative.
44eb6ee1 342 if (!file_exists($dst) && !imagecache_build_derivative($preset['actions'], $src, $dst)) {
42669fda 343 // Generate an error if image could not generate.
650d9de9 344 watchdog('imagecache', t('Failed generating an image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset)), WATCHDOG_ERROR);
42669fda 345 header("HTTP/1.0 500 Internal Server Error");
42669fda
DP
346 exit;
347 }
348 imagecache_transfer($dst);
349}
1c784f3b 350
f490ff7d 351function _imagecache_apply_action($action, &$image) {
650d9de9
DP
352 $actions = imagecache_action_definitions();
353
1ae0e73d 354 if ($definition = imagecache_action_definition($action['action'])) {
650d9de9
DP
355 return call_user_func($action['action'] .'_image', $image, $action['data']);
356 }
357 // skip undefined actions.. module probably got uninstalled or disabled.
358 watchdog('imagecache', t('non-existant action %action', array('%action' => $action['action'])), WATCHDOG_NOTICE);
359 return TRUE;
360}
361
42669fda
DP
362/**
363 * helper function to transfer files from imagecache. Determines mime type and sets a last modified header.
364 * @param $path path to file to be transferred.
365 * @return <exit>
366 */
1c784f3b 367
42669fda
DP
368function imagecache_transfer($path) {
369 if (function_exists('mime_content_type')) {
370 $mime = mime_content_type($path);
3edf6c97 371 }
42669fda
DP
372 else {
373 $size = getimagesize($path);
374 $mime = $size['mime'];
375 }
376 $headers = array('Content-Type: '. mime_header_encode($mime));
12c7ea17 377
42669fda 378 if ($fileinfo = stat($path)) {
12c7ea17
DP
379 $headers[] = 'Content-Length: '. $fileinfo[7];
380 _imagecache_cache_set_cache_headers($fileinfo, $headers);
3edf6c97 381 }
42669fda
DP
382 file_transfer($path, $headers);
383 exit;
384}
3edf6c97 385
650d9de9 386/**
12c7ea17
DP
387 * Set file headers that handle "If-Modified-Since" correctly for the
388 * given fileinfo. Most code has been taken from drupal_page_cache_header().
389 */
390function _imagecache_cache_set_cache_headers($fileinfo, &$headers) {
391 // Set default values:
392 $last_modified = gmdate('D, d M Y H:i:s', $fileinfo[9]) .' GMT';
393 $etag = '"'. md5($last_modified) .'"';
394
395 // See if the client has provided the required HTTP headers:
396 $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
397 ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
398 : FALSE;
399 $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
400 ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
401 : FALSE;
402
403 if ($if_modified_since && $if_none_match
404 && $if_none_match == $etag // etag must match
405 && $if_modified_since == $last_modified) { // if-modified-since must match
406 header('HTTP/1.1 304 Not Modified');
407 // All 304 responses must send an etag if the 200 response
408 // for the same object contained an etag
409 header('Etag: '. $etag);
410 // We must also set Last-Modified again, so that we overwrite Drupal's
411 // default Last-Modified header with the right one
412 header('Last-Modified: '. $last_modified);
413 exit;
414 }
415
416 // Send appropriate response:
417 $headers[] = 'Last-Modified: '. $last_modified;
418 $headers[] = 'ETag: '. $etag;
419}
420
421/**
650d9de9
DP
422 * A recursive mkdir.
423 */
424function _imagecache_mkdir($dir) {
425 $folders = explode("/", $dir);
426
427 foreach ($folders as $folder) {
428 $dirs[] = $folder;
429 $path = implode("/", $dirs);
430 if (is_dir($path)) {
431 continue;
432 }
433 if (is_file($path)) {
434 watchdog('imagecache', t('file exists where we would like a directory: %path', array('%path' => $path)), WATCHDOG_ERROR);
435 return FALSE;
436 }
437 if (!@mkdir($path)) {
438 watchdog('imagecache', t('Could not create destination: %dir halted at: %path', array('%dir' => $dir, '%path' => $path)), WATCHDOG_ERROR);
439 return FALSE;
440 }
4ac2968f
DP
441 if (!@chmod($path, 0775)) {
442 watchdog('imagecache', t('Could not set permissons on created directory: %dir halted at: %path', array('%dir' => $dir, '%path' => $path)), WATCHDOG_ERROR);
443 return FALSE;
444 }
650d9de9
DP
445 }
446 return TRUE;
447}
448
42669fda
DP
449/**
450 * build an image cache derivative
451 *
12c7ea17
DP
452 * @param $actions Array of imagecache actions.
453 * @param $src Path of the source file.
454 * @param $dst Path of the destination file.
455 * @param $tmp Path of the temporary file used for manipulating the image.
42669fda
DP
456 * @return TRUE - derivative generated, FALSE - no derivative generated, NULL - derivative being generated
457 */
44eb6ee1 458function imagecache_build_derivative($actions, $src, $dst) {
42669fda
DP
459 // get the folder for the final location of this preset...
460 $dir = dirname($dst);
fa508ec3 461
42669fda 462 // Build the destination folder tree if it doesn't already exists.
650d9de9
DP
463 if (!file_check_directory($dir) && !_imagecache_mkdir($dir)) {
464 watchdog('imagecache', t('Failed to create imagecache directory: %dir', array('%dir' => $dir)), WATCHDOG_ERROR);
465 return FALSE;
3edf6c97 466 }
277b7f55 467
44eb6ee1
DP
468 if (!$image = imageapi_image_open($src)) {
469 return FALSE;
470 }
471
159f43cf 472
42669fda 473 foreach ($actions as $action) {
9ba6a64d
DP
474 if (!empty($action['data'])) {
475 // QuickSketch, why do these run first/twice? - dopry.
476 if (isset($action['data']['width'])) {
477 $width = _imagecache_filter('width', $action['data']['width'], $image->info['width'], $image->info['height']);
478 }
479 if (isset($action['data']['height'])) {
480 $height = _imagecache_filter('height', $action['data']['height'], $image->info['width'], $image->info['height']);
481 }
f3ede8cc 482 foreach ($action['data'] as $key => $value) {
9ba6a64d 483 $action['data'][$key] = _imagecache_filter($key, $value, $image->info['width'], $image->info['height'], $width, $height);
f3ede8cc 484 }
1c784f3b 485 }
f490ff7d 486 if (!_imagecache_apply_action($action, $image)) {
650d9de9
DP
487 watchdog( 'imagecache', t('action(id:%id): %action failed for %src', array('%id' => $action['actionid'], '%action' => $action['action'], '%src' => $src)), WATCHDOG_ERROR);
488 return FALSE;
1c784f3b 489 }
1c784f3b 490 }
9ba6a64d 491
44eb6ee1 492 imageapi_image_close($image, $dst);
9ba6a64d 493
44eb6ee1 494 if (!imageapi_image_close($image, $dst)) {
9ba6a64d
DP
495 if (file_exists($dst)) {
496 watchdog('imagecache', t('Cached image file already exists. There is an issue with your Rewrite configuration.'), WATCHDOG_ERROR);
497 }
650d9de9
DP
498 return FALSE;
499 }
500 return TRUE;
1c784f3b
DP
501}
502
42669fda
DP
503
504
5c9ebf68
DP
505function imagecache_imagefield_file($op, $file) {
506 switch($op) {
507 // Delete imagecache presets when imagecache images are deleted.
508 case 'delete': imagecache_image_flush($file['filepath']); break;
42669fda 509
5c9ebf68
DP
510 // Create imagecache derivatives when files are saved.
511 case 'save':
512 break;
513 }
514
515}
42669fda 516
1c784f3b
DP
517/**
518 * Implementation of hook_field_formatter_info().
519 */
520function imagecache_field_formatter_info() {
42cc9535
DP
521 foreach (imagecache_presets() as $preset) {
522 $formatters[$preset['presetname']] = array(
523 'label' => $preset['presetname'],
1c784f3b
DP
524 'field types' => array('image'),
525 );
42cc9535 526 $formatters[$preset['presetname'] .'_linked'] = array(
71ea9e27
DP
527 'label' => t('@preset as link to node', array('@preset' => $preset['presetname'])),
528 'field types' => array('image'),
529 );
530 $formatters[$preset['presetname'] .'_imagelink'] = array(
531 'label' => t('@preset as link to image', array('@preset' => $preset['presetname'])),
1c784f3b
DP
532 'field types' => array('image'),
533 );
534 }
535 return $formatters;
536}
537
538/**
539 * Implementation of hook_field_formatter().
540 */
541function imagecache_field_formatter($field, $item, $formatter) {
f490ff7d 542 if (empty($item['fid']) && $field['use_default_image']) {
44eb6ee1 543 $item = $field['default_image'];
1c784f3b
DP
544 }
545 // Views does not load the file for us, while CCK display fields does.
546 if (!isset($item['filepath'])) {
02db7e75 547 $item = array_merge($item, _imagecache_file_load($item['fid']));
1c784f3b 548 }
42cc9535 549
02db7e75
DP
550 $presetname = $formatter;
551 $presetname = preg_replace('/_linked$/', '', $presetname);
552 $presetname = preg_replace('/_imagelink$/', '', $presetname);
42cc9535 553 if ($preset = imagecache_preset_by_name($presetname)) {
02db7e75 554 return theme('imagecache_formatter', $field, $item, $presetname);
1c784f3b
DP
555 }
556}
557
558function _imagecache_file_load($fid = NULL) {
559 // Don't bother if we weren't passed an fid.
560 if (isset($fid) && is_numeric($fid)) {
561 $result = db_query('SELECT * FROM {files} WHERE fid = %d', $fid);
562 $file = db_fetch_array($result);
65d53f72 563 }
1c784f3b 564 return ($file) ? $file : array();
65d53f72 565}
e0cb144b 566
e0cb144b 567
1c784f3b
DP
568/**
569 * Filter key word values such as 'top', 'right', 'center', and also percentages.
570 * All returned values are in pixels relative to the passed in height and width.
571 */
572function _imagecache_filter($key, $value, $current_width, $current_height, $new_width = NULL, $new_height = NULL) {
573 switch ($key) {
574 case 'width':
575 $value = _imagecache_percent_filter($value, $current_width);
576 break;
577 case 'height':
578 $value = _imagecache_percent_filter($value, $current_height);
579 break;
580 case 'xoffset':
581 $value = _imagecache_keyword_filter($value, $current_width, $new_width);
582 break;
583 case 'yoffset':
584 $value = _imagecache_keyword_filter($value, $current_height, $new_height);
585 break;
586 }
587 return $value;
588}
589
590/**
591 * Accept a percentage and return it in pixels.
592 */
593function _imagecache_percent_filter($value, $current_pixels) {
594 if (strpos($value, '%') !== FALSE) {
595 $value = str_replace('%', '', $value) * 0.01 * $current_pixels;
596 }
597 return $value;
598}
599
600/**
601 * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
602 */
603function _imagecache_keyword_filter($value, $current_pixels, $new_pixels) {
604 switch ($value) {
605 case 'top':
606 case 'left':
607 $value = 0;
608 break;
609 case 'bottom':
610 case 'right':
611 $value = $current_pixels - $new_pixels;
612 break;
613 case 'center':
614 $value = $current_pixels/2 - $new_pixels/2;
615 break;
616 }
617 return $value;
618}
619
2d6b7863 620/**
650d9de9 621 * Recursively delete all files and folders in the specified filepath, then
1c784f3b
DP
622 * delete the containing folder.
623 *
624 * Note that this only deletes visible files with write permission.
2d6b7863
DP
625 *
626 * @param string $path
1c784f3b 627 * A filepath relative to file_directory_path.
2d6b7863
DP
628 */
629function _imagecache_recursive_delete($path) {
1c784f3b
DP
630 $listing = $path .'/*';
631 foreach (glob($listing) as $file) {
632 if (is_file($file) === TRUE) {
2d6b7863
DP
633 @unlink($file);
634 }
1c784f3b 635 elseif (is_dir($file) === TRUE) {
2d6b7863 636 _imagecache_recursive_delete($file);
2d6b7863
DP
637 }
638 }
639 @rmdir($path);
e0cb144b
DP
640}
641
e0cb144b 642/**
30fa0037
DP
643 * Theme an img tag for displaying the image, with or without link.
644 */
1c784f3b 645function theme_imagecache_formatter($field, $item, $formatter) {
6491b9c4 646 $item['filepath'] = $item['fid'] == 'upload' ? $item['preview'] : $item['filepath'];
1c784f3b
DP
647 if (preg_match('/_linked$/', $formatter)) {
648 $formatter = preg_replace('/_linked$/', '', $formatter);
649 $image = theme('imagecache', $formatter, $item['filepath'], $item['alt'], $item['title']);
650 $output = l($image, 'node/'. $item['nid'], array(), NULL, NULL, FALSE, TRUE);
651 }
71ea9e27
DP
652 else if (preg_match('/_imagelink$/', $formatter)) {
653 $formatter = preg_replace('/_imagelink$/', '', $formatter);
654 $output = theme('imagecache_imagelink', $formatter, $item['filepath'], $item['alt'], $item['title']);
655 }
1c784f3b
DP
656 else {
657 $output = theme('imagecache', $formatter, $item['filepath'], $item['alt'], $item['title']);
658 }
659 return $output;
660}
661
b59dc14e
DP
662function theme_imagecache($namespace, $path, $alt = '', $title = '', $attributes = NULL) {
663 $attributes = drupal_attributes($attributes);
b7c5115b 664 $imagecache_path = imagecache_create_url($namespace, $path);
b59dc14e 665 return '<img src="'. $imagecache_path .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $attributes .' />';
505fd29e 666}
b9f4e1af 667
71ea9e27
DP
668function theme_imagecache_imagelink($namespace, $path, $alt = '', $title = '', $attributes = NULL) {
669 $image = theme('imagecache', $namespace, $path, $alt, $title);
670 $original_image_url = file_create_url($path);
671 return l($image, $original_image_url, array(), NULL, NULL, FALSE, TRUE);
672}
673
650d9de9 674
42cc9535
DP
675/************************************************************************************
676 * ImageCache action implementation example in module.
677 */
f490ff7d
DP
678function imagecache_resize_image(&$image, $data) {
679 if (!imageapi_image_resize($image, $data['width'], $data['height'])) {
650d9de9
DP
680 watchdog('imagecache', t('imagecache_resize_image failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, TRUE))), WATCHDOG_ERROR);
681 return FALSE;
682 }
f490ff7d 683 return TRUE;
650d9de9
DP
684}
685
686function imagecache_resize_form($action) {
687 $form['width'] = array(
688 '#type' => 'textfield',
689 '#title' => t('Width'),
690 '#default_value' => $action['width'],
691 '#description' => t('Enter a width in pixels or as a percentage. i.e. 500 or 80%.'),
692 );
693 $form['height'] = array(
694 '#type' => 'textfield',
695 '#title' => t('Height'),
696 '#default_value' => $action['height'],
697 '#description' => t('Enter a height in pixels or as a percentage. i.e. 500 or 80%.'),
698 );
699 return $form;
700}
701
42cc9535
DP
702function theme_imagecache_resize($element) {
703 $data = $element['#value'];
704 return 'width: '. $data['width'] .', height: '. $data['height'];
705}
706
707
708
709/**
710 * ImageCache 2.x API
711 *
712 * The API for imagecache has changed. There is a compatibility layer for
713 * imagecache 1.x. Please see the imagecache_compat.module
714 *
715 * The 2.x API returns more structured data, has shorter function names, and
716 * implements more aggressive metadata caching.
717 *
718 */
719
720/**
721 * Get an array of all presets and their settings.
722 *
723 * @param reset
724 * if set to true it will clear the preset cache
725 *
726 * @return
727 * array of presets array( $preset_id => array('presetid' => integer, 'presetname' => string))
728 */
729function imagecache_presets($reset = FALSE) {
730 static $presets = array();
731
732 // Clear caches if $reset is TRUE;
733 if ($reset) {
734 $presets = array();
735 cache_clear_all('imagecache:presets', 'cache');
736
737 // Clear the content.module cache (refreshes the list of formatters provided by imagefield.module).
738 if (module_exists('content')) {
739 content_clear_type_cache();
740 }
741 }
42cc9535
DP
742 // Return presets if the array is populated.
743 if (!empty($presets)) {
744 return $presets;
745 }
746
747 // Grab from cache or build the array.
748 if ($cache = cache_get('imagecache:presets', 'cache')) {
749 $presets = unserialize($cache->data);
750 }
751 else {
752 $result = db_query('SELECT * FROM {imagecache_preset} ORDER BY presetname');
753 while ($preset = db_fetch_array($result)) {
754 $presets[$preset['presetid']] = $preset;
755 $presets[$preset['presetid']]['actions'] = imagecache_preset_actions($preset);
756 }
757 cache_set('imagecache:presets', 'cache', serialize($presets));
758 }
759 return $presets;
760}
761
762/**
763 * Load a preset by preset_id.
764 *
765 * @param preset_id
766 * The numeric id of a preset.
767 *
768 * @return
769 * preset array( 'presetname' => string, 'presetid' => integet)
770 * empty array if preset_id is an invalid preset
771 */
772function imagecache_preset($preset_id, $reset = FALSE) {
773 $presets = imagecache_presets($reset);
774 return (isset($presets[$preset_id])) ? $presets[$preset_id] : array();
775}
776
777/**
778 * Load a preset by name.
779 *
780 * @param preset_name
781 *
782 * @return
783 * preset array( 'presetname' => string, 'presetid' => integer)
784 * empty array if preset_name is an invalid preset
785 */
786
787function imagecache_preset_by_name($preset_name) {
788 static $presets_by_name = array();
789 if (!$presets_by_name && $presets = imagecache_presets()) {
790 foreach ($presets as $preset) {
791 $presets_by_name[$preset['presetname']] = $preset;
792 }
793 }
794 return (isset($presets_by_name[$preset_name])) ? $presets_by_name[$preset_name] : array();
795}
796
797/**
798 * Save an ImageCache preset.
799 *
800 * @param preset
801 * an imagecache preset array.
802 * @return
803 * a preset array. In the case of a new preset, 'presetid' will be populated.
804 */
805function imagecache_preset_save($preset) {
806 // @todo: CRUD level validation?
807 if (isset($preset['presetid']) && is_numeric($preset['presetid'])) {
808 db_query('UPDATE {imagecache_preset} SET presetname =\'%s\' WHERE presetid = %d', $preset['presetname'], $preset['presetid']);
809 }
810 else {
811 $preset['presetid'] = db_next_id('{imagecache_preset}_presetid');
812 db_query('INSERT INTO {imagecache_preset} (presetid, presetname) VALUES (%d, \'%s\')', $preset['presetid'], $preset['presetname']);
813 }
814
815 // Reset presets cache.
816 imagecache_preset_flush($preset);
817 imagecache_presets(TRUE);
818 return $preset;
819}
820
821function imagecache_preset_delete($preset) {
822 imagecache_preset_flush($preset['presetid']);
823 db_query('DELETE FROM {imagecache_action} where presetid = %d', $preset['presetid']);
824 db_query('DELETE FROM {imagecache_preset} where presetid = %d', $preset['presetid']);
825 imagecache_presets(TRUE);
826 return TRUE;
827}
828
829function imagecache_preset_actions($preset, $reset = FALSE) {
830 static $actions_cache = array();
831
832 if ($reset || empty($actions_cache[$preset['presetid']])) {
833 $result = db_query('SELECT * FROM {imagecache_action} where presetid = %d order by weight', $preset['presetid']);
834 while ($row = db_fetch_array($result)) {
835 $row['data'] = unserialize($row['data']);
836 $actions_cache[$preset['presetid']][] = $row;
837 }
838 }
839
840 return isset($actions_cache[$preset['presetid']]) ? $actions_cache[$preset['presetid']] : array();
841}
842
843/**
844 * Flush cached media for a preset.
845 * @param id
846 * A preset id.
847 */
848function imagecache_preset_flush($preset) {
849 if (user_access('flush imagecache')) {
850 $presetdir = realpath(file_directory_path() .'/imagecache/'. $preset['presetname']);
851 if (is_dir($presetdir)) {
852 _imagecache_recursive_delete($presetdir);
853 }
854 }
855}
856
857/**
858 * Clear cached versions of a specific file in all presets.
859 * @param $path
860 * The Drupal file path to the original image.
861 */
862function imagecache_image_flush($path) {
863 foreach (imagecache_presets() as $preset) {
5c9ebf68 864 file_delete(imagecache_create_path($preset['presetname'], $path));
42cc9535
DP
865 }
866}
867
868function imagecache_action($actionid) {
1ae0e73d
DP
869 static $actions;
870
871 if (!isset($actions[$actionid])) {
872 $action = array();
873
874 $result = db_query('SELECT * FROM {imagecache_action} WHERE actionid=%d', $actionid);
875 if ($row = db_fetch_array($result)) {
876 $action = $row;
877 $action['data'] = unserialize($action['data']);
878
879 $definition = imagecache_action_definition($action['action']);
880 $action = array_merge($definition, $action);
881 $actions[$actionid] = $action;
882 }
883 }
884 return $actions[$actionid];
42cc9535
DP
885}
886
887function imagecache_action_save($action) {
888 if ($action['actionid']) {
889 db_query("UPDATE {imagecache_action} SET weight=%d, data='%s' WHERE actionid=%d", $action['weight'], serialize($action['data']), $action['actionid']);
890 }
891 else {
892 $action['actionid'] = db_next_id('{imagecache_action}_actionid');
893 db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, action, data) VALUES (%d, %d, %d,'%s', '%s')", $action['actionid'], $action['presetid'], $action['weight'], $action['action'], serialize($action['data']));
894 }
895 $preset = imagecache_preset($action['presetid']);
896 imagecache_preset_flush($preset);
897 imagecache_presets(TRUE);
898 return $action;
899}
900
901function imagecache_action_delete($action) {
902 db_query("DELETE FROM {imagecache_action} WHERE actionid=%d", $action['actionid']);
903 $preset = imagecache_preset($action['presetid']);
904 imagecache_preset_flush($preset);
905 imagecache_presets(TRUE);
906}
a09d5c25
DP
907
908