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