/path
*
* @todo: improve image resizing, reduce distortion.
* @todo: add watermarking capabilities.
* @todo: split action/handlers into their own little .inc files.
* @todo: enforce permissions.
*
* Notes:
* To add a new handler,
* add fields and select option to _imagecache_actions_form;
* add handling code to imagecache_cache
*
*/
/**
* Implementation of hook_perm().
*/
function imagecache_perm() {
return array('administer imagecache', 'flush imagecache');
}
/**
* Implementation of hook_menu().
*/
function imagecache_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => file_directory_path() .'/imagecache',
'callback' => 'imagecache_cache',
'access' => TRUE,
'type' => MENU_CALLBACK
);
$items[] = array(
'path' => 'admin/settings/imagecache',
'title' => t('Image cache'),
'description' => t('Configure rulesets and actions for imagecache.'),
'access' => user_access('administer imagecache'),
'callback' => 'drupal_get_form',
'callback arguments' => array('imagecache_admin'),
);
}
return $items;
}
/**
* Implementation of hook_requirements().
*/
function imagecache_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time.
$t = get_t();
if ($phase == 'runtime') {
// Clean URLS.
if (!variable_get('clean_url', 0)) {
$requirements['clean_urls'] = array(
'title' => $t('Clean URLs'),
'value' => $t('Not enabled'),
'severity' => REQUIREMENT_ERROR,
'description' => $t('Imagecache will not operate properly if Clean URLs is not enabled on your site.', array('!url' => url('admin/settings/clean-urls'))),
);
}
// Check for an image library.
if (count(image_get_available_toolkits()) == 0) {
$requirements['clean_urls'] = array(
'title' => $t('Image Toolkit'),
'value' => $t('No image toolkits available'),
'severity' => REQUIREMENT_ERROR,
'description' => $t('Imagecache requires an imagetoolkit such as GD2 or Imagemagick be installed on your server.'),
);
}
// Check for public files.
if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
$requirements['file_system'] = array(
'title' => $t('File Download Method'),
'value' => $t('Private Downloads'),
'severity' => REQUIREMENT_ERROR,
'description' => $t('Imagecache will not operate properly using Private Files. Please enable Public File Transfer.', array('!url' => url('admin/settings/file-system'))),
);
}
// Check for JPEG/PNG/GIF support.
if ('gd' == image_get_toolkit()) {
foreach (array('gif', 'jpeg', 'png') as $format) {
if (!function_exists('imagecreatefrom'. $format)) {
$requirements['gd_'. $format] = array(
'title' => $t('GD !format Support', array('!format' => drupal_ucfirst($format))),
'value' => $t('Not installed'),
'severity' => REQUIREMENT_INFO,
'description' => $t('PHP was not compiled with %format support. Imagecache will not be able to process %format images.', array('%format' => $format)),
);
}
}
}
}
return $requirements;
}
/*
* @return boolean
* @param string $link
* @desc ēZberprēZft die angegeben URL auf Erreichbarkeit (HTTP-Code: 200)
# @ref http://www.php.net/manual/en/function.fsockopen.php#39948
*/
function url_validate( $link )
{
$url_parts = @parse_url( $link );
if ( empty( $url_parts["host"] ) ) return( false );
if ( !empty( $url_parts["path"] ) )
{
$documentpath = $url_parts["path"];
}
else
{
$documentpath = "/";
}
if ( !empty( $url_parts["query"] ) )
{
$documentpath .= "?" . $url_parts["query"];
}
$host = $url_parts["host"];
$port = $url_parts["port"];
// Now (HTTP-)GET $documentpath at $host";
if (empty( $port ) ) $port = "80";
$socket = @fsockopen( $host, $port, $errno, $errstr, 30 );
if (!$socket)
{
return(false);
}
else
{
fwrite ($socket, "HEAD ".$documentpath." HTTP/1.0\r\nHost: $host\r\n\r\n");
$http_response = fgets( $socket, 22 );
if ( ereg("200 OK", $http_response, $regs ) )
{
fclose( $socket );
return(true);
}
else
{
// echo "HTTP-Response: $http_response
";
return(false);
}
}
}
function imagecache_cache() {
$generated = FALSE;
$args = func_get_args();
$preset = array_shift($args);
$preset_id = _imagecache_preset_load_by_name($preset);
// Exit if preset is not found, a small performance and sec fix.
if (!$preset_id) {
exit;
}
$actions = _imagecache_actions_get_by_presetid($preset_id);
// check if it is a remote file by magic prefix 'ooxx_in_...'
// if it is, restore the url back, Drake
$is_remote = false;
if( false === strpos($args[0], 'ooxx_in_http')) {
if( false === strpos($args[0], 'ooxx_in_https')) {
}
else { // https://
$is_remote = true;
$tmpargs = $args;
$tmpargs[0] = 'https:/';
$url_path = implode('/', $tmpargs);
}
}
else { // http://
$is_remote = true;
$tmpargs = $args;
$tmpargs[0] = 'http:/';
$url_path = implode('/', $tmpargs);
}
$path = implode('/', $args);
// Verify that the source exists, if not exit. Maybe display a missing image.
// make use of url_validate to do verification, Drake
if( $is_remote ) {
$source = $url_path;
if( !url_validate( $source) ) {
return drupal_not_found();
//@todo: Add a global not found image, and a not found
//image per preset, and deliver here if the preset
//is flagged to provide a not found image.
}
}
else {
$source = file_create_path($path);
if (!is_file($source)) {
return drupal_not_found();
// @todo: Add a global not found image, and a not found image per preset,
// and deliver here if the preset is flagged to provide a not found image.
}
}
$destination = file_create_path() .'/imagecache/'. $preset .'/'. $path;
// Prepend presetname to tmp file name to prevent namespace clashes when
// multiple presets for the same image are called in rapid succession.
$tmpdestination = file_directory_temp() .'/'. $preset . str_replace(dirname($path) .'/', '', $path);
$dir = dirname($destination);
// Build the destination folder tree if it doesn't already exists.
if (!file_check_directory($dir)) {
$folders = explode("/", $dir);
foreach ($folders as $folder) {
$tpath[] = $folder;
$checkpath = implode("/", $tpath);
if (!file_check_directory($checkpath)) {
$p = implode("/", $tpath);
if (!file_exists($p)) {
mkdir($p);
}
}
}
}
if (!file_check_directory($dir)) {
watchdog('imagecache', t('Could not create destination: %dir', array('%dir' => $destination)), WATCHDOG_ERROR);
return;
}
// Check if file exists to prevent multiple apache children from trying to generate.
if (!is_file($tmpdestination)) {
$generated = TRUE;
// copy the remote file to the local tmp destination, Drake
if( $is_remote ) {
if (!@copy($source, $tmpdestination)) {
drupal_set_message(t('The selected file %file could not be copied to %dest.', array('%file' => $source,'%dest' => $tmpdestination)), 'error');
}
}
else {
file_copy($source, $tmpdestination);
}
foreach ($actions as $action) {
$size = getimagesize($tmpdestination);
$new_width = _imagecache_filter('width', $action['data']['width'], $size[0], $size[1]);
$new_height = _imagecache_filter('height', $action['data']['height'], $size[0], $size[1]);
foreach ($action['data'] as $key => $value) {
$action['data'][$key] = _imagecache_filter($key, $value, $size[0], $size[1], $new_width, $new_height);
}
switch ($action['data']['function']) {
case 'resize':
if (!image_resize($tmpdestination, $tmpdestination, $action['data']['width'], $action['data']['height'])) {
watchdog('imagecache', t('Imagecache resize action ID %id failed.', array('%id' => $action['actionid'])), WATCHDOG_ERROR);
}
break;
case 'scale':
if ($action['data']['fit'] == 'outside' && $action['data']['width'] && $action['data']['height']) {
$ratio = $size[0]/$size[1];
$new_ratio = $action['data']['width']/$action['data']['height'];
$action['data']['width'] = $ratio > $new_ratio ? 0 : $action['data']['width'];
$action['data']['height'] = $ratio < $new_ratio ? 0 : $action['data']['height'];
}
// Set impossibly large values if the width and height aren't set.
$action['data']['width'] = $action['data']['width'] ? $action['data']['width'] : 9999999;
$action['data']['height'] = $action['data']['height'] ? $action['data']['height'] : 9999999;
if (!image_scale($tmpdestination, $tmpdestination, $action['data']['width'], $action['data']['height'])) {
watchdog('imagecache', t('Imagecache scale action ID %id failed.', array('%id' => $action['actionid'])), WATCHDOG_ERROR);
}
break;
case 'crop':
if (!image_crop($tmpdestination, $tmpdestination, $action['data']['xoffset'], $action['data']['yoffset'], $action['data']['width'], $action['data']['height'])) {
watchdog('imagecache', t('Imagecache crop action ID %id failed.', array('%id' => $action['actionid'])), WATCHDOG_ERROR);
}
break;
}
}
file_move($tmpdestination, $destination);
}
else {
$generated = TRUE;
}
if ($generated) {
if (function_exists('mime_content_type')) {
$mime = mime_content_type($destination);
}
else {
$size = getimagesize($destination);
$mime = $size['mime'];
}
file_transfer($destination, array('Content-Type: '. mime_header_encode($mime), 'Content-Length: '. filesize($destination)));
}
else {
// Generate an error if image could not generate.
watchdog('imagecache', t('There were problems generating an image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset['presetname'])), WATCHDOG_ERROR);
}
}
/**
* Implementation of hook_field_formatter_info().
*/
function imagecache_field_formatter_info() {
$rules = _imagecache_get_presets();
foreach ($rules as $ruleid => $rulename) {
$formatters[$rulename] = array(
'label' => $rulename,
'field types' => array('image'),
);
$formatters[$rulename .'_linked'] = array(
'label' => $rulename .' as link',
'field types' => array('image'),
);
}
return $formatters;
}
/**
* Implementation of hook_field_formatter().
*/
function imagecache_field_formatter($field, $item, $formatter) {
if (!isset($item['fid'])) {
return '';
}
// Views does not load the file for us, while CCK display fields does.
if (!isset($item['filepath'])) {
$file = _imagecache_file_load($item['fid']);
$item = array_merge($item, $file);
}
$rules = _imagecache_get_presets();
$formatter_check = preg_replace('/_linked$/', '', $formatter);
if (in_array($formatter_check, (array) $rules)) {
return theme('imagecache_formatter', $field, $item, $formatter);
}
}
function _imagecache_file_load($fid = NULL) {
// Don't bother if we weren't passed an fid.
if (isset($fid) && is_numeric($fid)) {
$result = db_query('SELECT * FROM {files} WHERE fid = %d', $fid);
$file = db_fetch_array($result);
}
return ($file) ? $file : array();
}
function _imagecache_get_presets($reset = FALSE) {
static $presets = array();
// Check caches if $reset is FALSE;
if (!$reset) {
if (!empty($presets)) {
return $presets;
}
// Grab from cache saves building the array.
// Plus it's a frequently used table.
$cache = cache_get('imagecache:presets', 'cache');
$presets = unserialize($cache->data);
// If the preset is not an array, cache_clear_all has been called
// there no/invalid data in the cache. Fall through and repopulate cache;
if (is_array($presets)) {
return $presets;
}
}
// Load Data from the database on reset or if we get invalid data from the array.
$presets = array();
$result = db_query('SELECT presetid, presetname FROM {imagecache_preset} ORDER BY presetname');
while ($row = db_fetch_array($result)) {
$presets[$row['presetid']] = $row['presetname'];
}
cache_set('imagecache:presets', 'cache', serialize($presets));
// Clear the content.module cache (refreshes the list of formatters provided by imagefield.module).
if (module_exists('content')) {
content_clear_type_cache();
}
return $presets;
}
function _imagecache_actions_get_by_presetid($presetid) {
$actions = array();
$result = db_query('SELECT actionid, weight, data FROM {imagecache_action} where presetid = %d order by weight', $presetid);
while ($row = db_fetch_array($result)) {
$row['data'] = unserialize($row['data']);
$actions[$row['actionid']] = $row;
}
return $actions;
}
/**
* Filter key word values such as 'top', 'right', 'center', and also percentages.
* All returned values are in pixels relative to the passed in height and width.
*/
function _imagecache_filter($key, $value, $current_width, $current_height, $new_width = NULL, $new_height = NULL) {
switch ($key) {
case 'width':
$value = _imagecache_percent_filter($value, $current_width);
break;
case 'height':
$value = _imagecache_percent_filter($value, $current_height);
break;
case 'xoffset':
$value = _imagecache_keyword_filter($value, $current_width, $new_width);
break;
case 'yoffset':
$value = _imagecache_keyword_filter($value, $current_height, $new_height);
break;
}
return $value;
}
/**
* Accept a percentage and return it in pixels.
*/
function _imagecache_percent_filter($value, $current_pixels) {
if (strpos($value, '%') !== FALSE) {
$value = str_replace('%', '', $value) * 0.01 * $current_pixels;
}
return $value;
}
/**
* Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
*/
function _imagecache_keyword_filter($value, $current_pixels, $new_pixels) {
switch ($value) {
case 'top':
case 'left':
$value = 0;
break;
case 'bottom':
case 'right':
$value = $current_pixels - $new_pixels;
break;
case 'center':
$value = $current_pixels/2 - $new_pixels/2;
break;
}
return $value;
}
function imagecache_admin() {
drupal_set_title('Imagecache administration');
$form = array();
$form['title'] = array('#type' => 'markup', '#value' => t('Imagecache presets'));
$form['presets']['#tree'] = TRUE;
$presets = _imagecache_get_presets();
foreach ($presets as $presetid => $presetname) {
$form['presets'][$presetid] = array(
'#type' => 'fieldset',
'#title' => t($presetname),
'#collapsible' => TRUE,
'#collapsed' => arg(4) != $presetid,
);
$form['presets'][$presetid]['name'] = array(
'#type' => 'textfield',
'#title' => t('Preset namespace'),
'#default_value' => $presetname,
'#description' => t('String that will be used as an identifier in the url for this set of handlers. Final urls will look like http://example.com/files/imagecache/%namespace/<path to orig>', array('%namespace' => $presetname)),
);
$form['presets'][$presetid]['handlers'] = array(
'#type' => 'fieldset',
'#title' => t('Image handlers'),
);
$form['presets'][$presetid]['handlers']['#tree'] = FALSE;
$form['presets'][$presetid]['handlers'] = _imagecache_actions_form($presetid);
$form['presets'][$presetid]['ops']['#tree'] = FALSE;
$form['presets'][$presetid]['ops']['update'] = array(
'#type' => 'submit',
'#name' => 'preset-op['. $presetid .']',
'#value' => t('Update preset'),
);
$form['presets'][$presetid]['ops']['delete'] = array(
'#type' => 'submit',
'#name' => 'preset-op['. $presetid .']',
'#value' => t('Delete preset'),
);
$form['presets'][$presetid]['ops']['flush'] = array(
'#type' => 'submit',
'#name' => 'preset-op['. $presetid .']',
'#value' => t('Flush preset images'),
);
}
$form['presets']['new'] = array(
'#type' => 'fieldset',
'#title' => t('New preset'),
'#tree' => TRUE,
);
$form['presets']['new']['name'] = array(
'#type' => 'textfield',
'#size' => '64',
'#title' => t('Preset namespace'),
'#default_value' => '',
'#description' => t('The namespace of an imagecache preset. It represents a series of actions to be performed when imagecache dynamically generates an image. This will also be used in the url for images. Please no spaces.'),
);
$form['presets']['new']['create'] = array(
'#type' => 'submit',
'#name' => 'preset-op[new]',
'#value' => t('Create preset'),
'#weight' => 10,
);
return $form;
}
function imagecache_admin_validate($form_id, $form_values) {
if (is_array($_POST['preset-op'])) {
foreach ($_POST['preset-op'] as $presetid => $op) {
// Check for illegal characters in preset names
if (preg_match('/[^0-9a-zA-Z_\-]/', $form_values['presets'][$presetid]['name'])) {
form_set_error('presets]['. $presetid .'][name', t('Please only use alphanumic characters, underscores (_), and hyphens (-) for preset names.'));
}
}
}
}
function imagecache_admin_submit($form_id, $form_values) {
if (is_array($_POST['preset-op'])) {
foreach ($_POST['preset-op'] as $presetid => $op) {
$presetid = check_plain($presetid);
switch ($op) {
case t('Create preset'):
_imagecache_preset_create($form_values['presets']['new']['name']);
break;
case t('Update preset'):
// Add new actions
$newaction = $form_values['presets'][$presetid]['handlers']['newaction'];
if ($newaction) {
$action = array();
$action['data'] = array('function' => $newaction);
$action['presetid'] = $presetid;
$action['weight'] = 0;
_imagecache_action_create($action);
}
// Update existing actions
foreach ($form_values['presets'][$presetid]['handlers'] as $actionid => $action) {
if ($actionid != 'newaction') {
$action['actionid'] = $actionid;
$action['presetid'] = $presetid;
$remove = $action['remove'];
unset($action['remove']);
$remove ? _imagecache_action_delete($action) : _imagecache_action_update($action);
}
}
// Update the entire preset.
_imagecache_preset_update($presetid, $form_values['presets'][$presetid]['name']);
break;
case t('Delete preset'):
_imagecache_preset_delete($presetid, $form_values['presets'][$presetid]['name']);
break;
case t('Flush preset images'):
_imagecache_preset_flush($presetid);
break;
}
}
}
}
/**
* Load a preset by id.
* @param id
* Preset id.
*/
function _imagecache_preset_load($id) {
$presets = _imagecache_get_presets();
return $presets[$id];
}
/**
* Load a preset by name.
* @param name
* Preset name.
*/
function _imagecache_preset_load_by_name($name) {
$presets = array_flip(_imagecache_get_presets());
return $presets[$name];
}
/**
* Create a preset.
* @param name
* Name of the preset to be created.
*/
function _imagecache_preset_create($name) {
$next_id = db_next_id('{imagecache_preset}_presetid');
db_query('INSERT INTO {imagecache_preset} (presetid, presetname) VALUES (%d, \'%s\')', $next_id, $name);
// Reset presets cache.
_imagecache_get_presets(TRUE);
$_REQUEST['destination'] = 'admin/settings/imagecache/preset/'. $next_id;
}
/**
* Update a preset.
* @param id
* Preset id.
* @param name
* new name for the preset
*/
function _imagecache_preset_update($id, $name) {
$name = check_plain($name);
$id = (int)$id;
_imagecache_preset_flush($id);
db_query('UPDATE {imagecache_preset} SET presetname =\'%s\' WHERE presetid = %d', $name, $id);
drupal_set_message(t('Updated preset "%name" (ID: @id)', array('%name' => $name, '@id' => $id)));
// Reset presets cache.
_imagecache_get_presets(TRUE);
$_REQUEST['destination'] = 'admin/settings/imagecache/preset/'. $id;
}
function _imagecache_preset_delete($id, $name) {
_imagecache_preset_flush($id);
db_query('DELETE FROM {imagecache_action} where presetid = %d', $id);
db_query('DELETE FROM {imagecache_preset} where presetid = %d', $id);
drupal_set_message(t('Preset "%name" (ID: @id) deleted.', array('%name' => $name, '@id' => $id)));
// Reset presets cache.
_imagecache_get_presets(TRUE);
}
/**
* Flush cached media for a preset.
* @param id
* A preset id.
*/
function _imagecache_preset_flush($id) {
if (user_access('flush imagecache')) {
drupal_set_message(t('Flushed Preset Images (ID: @id)', array('@id' => $id)));
$preset = _imagecache_preset_load($id);
$presetdir = realpath(file_directory_path() .'/imagecache/'. $preset);
if (is_dir($presetdir)) {
_imagecache_recursive_delete($presetdir);
}
}
}
/**
* Recursively delete all files and folders in the specified filepath, then
* delete the containing folder.
*
* Note that this only deletes visible files with write permission.
*
* @param string $path
* A filepath relative to file_directory_path.
*/
function _imagecache_recursive_delete($path) {
$listing = $path .'/*';
foreach (glob($listing) as $file) {
if (is_file($file) === TRUE) {
@unlink($file);
}
elseif (is_dir($file) === TRUE) {
_imagecache_recursive_delete($file);
}
}
@rmdir($path);
}
function _imagecache_action_create($action) {
//debug_msg($action, 'action@create: ');
$next_id = db_next_id('{imagecache_action}_actionid');
db_query('INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, %d, \'%s\')', $next_id, $action['presetid'], $action['weight'], serialize($action['data']));
}
function _imagecache_action_update($action) {
//debug_msg($action, 'action@update');
db_query('UPDATE {imagecache_action} SET weight = %d, data = \'%s\' WHERE actionid = %d', $action['weight'], serialize($action['data']), $action['actionid']);
}
function _imagecache_action_delete($action) {
_imagecache_preset_flush($action['presetid']);
db_query('DELETE FROM {imagecache_action} WHERE actionid = %d', $action['actionid']);
}
function _imagecache_actions_form($presetid) {
$form = array();
$actions = _imagecache_actions_get_by_presetid($presetid);
foreach ($actions as $actionid => $action) {
//debug_msg($action);
$form[$actionid] = array(
'#type' => 'fieldset',
'#title' => t($action['data']['function']),
);
$form[$actionid]['data']['function'] = array(
'#type' => 'hidden',
'#value' => $action['data']['function'],
);
$form[$actionid]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $action['weight'],
);
switch ($action['data']['function']) {
case 'scale':
$helptext = array();
$helptext['inside'] = t('Inside dimensions: Final dimensions will be less than or equal to the entered width and height. Useful for ensuring a maximum height and/or width.');
$helptext['outside'] = t('Outside dimensions: Final dimensions will be greater than or equal to the entered width and height. Ideal for cropping the result to a square.');
$description = '
$Id: imagecache.module,v 1.19.2.28 2007/06/16 17:22:41 dopry Exp $
'; $output .= '