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