'Generate image style', 'page callback' => 'image_style_generate', 'page arguments' => array(2), 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); return $items; } /** * Implement hook_theme(). */ function image_theme() { return array( 'image_style' => array( 'arguments' => array('style' => NULL, 'path' => NULL, 'alt' => '', 'title' => '', 'attributes' => array(), 'getsize' => TRUE), ), ); } /** * Implement hook_flush_caches(). */ function image_flush_caches() { return array('cache_image'); } /** * Implement hook_file_download(). * * Control the access to files underneath the styles directory. */ function image_file_download($filepath) { if (strpos($filepath, 'styles/') === 0) { $args = explode('/', $filepath); // Discard the first part of the path (styles). array_shift($args); // Get the style name from the second part. $style_name = array_shift($args); // Then the remaining parts are the path to the image. $original_path = implode('/', $args); // Check that the file exists and is an image. if ($info = image_get_info(file_create_path($filepath))) { // Check the permissions of the original to grant access to this image. $headers = module_invoke_all('file_download', $original_path); if (!in_array(-1, $headers)) { return array( 'Content-Type: ' . $info['mime_type'], 'Content-Length: ' . $info['file_size'], ); } } return -1; } } /** * Implement hook_file_move(). */ function image_file_move($file, $source) { // Delete any image derivatives at the original image path. image_path_flush($file->filepath); } /** * Implement hook_file_delete(). */ function image_file_delete($file) { // Delete any image derivatives of this image. image_path_flush($file->filepath); } /** * Clear cached versions of a specific file in all styles. * * @param $path * The Drupal file path to the original image. */ function image_path_flush($path) { $path = file_directory_strip($path); $styles = image_styles(); foreach ($styles as $style) { if ($path = file_create_path('styles/' . $style['name'] . '/' . $path)) { file_unmanaged_delete($path); } } } /** * Get an array of all styles and their settings. * * @return * An array of styles keyed by the image style ID (isid). * @see image_style_load() */ function image_styles() { $styles = &drupal_static(__FUNCTION__); // Grab from cache or build the array. if (!isset($styles)) { if ($cache = cache_get('image_styles', 'cache')) { $styles = $cache->data; } else { $styles = array(); $result = db_select('image_styles', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('image_styles') ->orderBy('name') ->execute(); foreach ($result as $style) { $styles[$style['name']] = $style; $styles[$style['name']]['effects'] = image_style_effects($style); } cache_set('image_styles', $styles); } } return $styles; } /** * Load a style by style name or ID. May be used as a loader for menu items. * * @param $name * The name of the style. * @param $isid * Optional. The numeric id of a style if the name is not known. * @return * An image style array containing the following keys: * - "isid": The unique image style ID. * - "name": The unique image style name. * - "effects": An array of effects within this style. * If the style name or ID is not valid, an empty array is returned. * @see image_effect_load() */ function image_style_load($name = NULL, $isid = NULL) { $styles = image_styles(); // If retrieving by name. if (isset($name) && isset($styles[$name])) { return $styles[$name]; } // If retrieving by image style id. if (isset($isid)) { foreach ($styles as $name => $style) { if ($style['isid'] == $isid) { return $style; } } } // Otherwise the style was not found. return FALSE; } /** * Save an image style. * * @param style * An image style array. * @return * A style array. In the case of a new style, 'isid' will be populated. */ function image_style_save($style) { if (isset($style['isid']) && is_numeric($style['isid'])) { // Load the existing style to make sure we account for renamed styles. $old_style = image_style_load(NULL, $style['isid']); image_style_flush($old_style); drupal_write_record('image_styles', $style, 'isid'); if ($old_style['name'] != $style['name']) { $style['old_name'] = $old_style['name']; } } else { drupal_write_record('image_styles', $style); $style['is_new'] = TRUE; } // Let other modules update as necessary on save. module_invoke_all('image_style_save', $style); // Clear all caches and flush. image_style_flush($style); return $style; } /** * Delete an image style. * * @param $style * An image style array. * @param $replacement_style_name * (optional) When deleting a style, specify a replacement style name so * that existing settings (if any) may be converted to a new style. * @return * TRUE on success. */ function image_style_delete($style, $replacement_style_name = '') { image_style_flush($style); db_delete('image_effects')->condition('isid', $style['isid'])->execute(); db_delete('image_styles')->condition('isid', $style['isid'])->execute(); // Let other modules update as necessary on save. $style['old_name'] = $style['name']; $style['name'] = $replacement_style_name; module_invoke_all('image_style_delete', $style); return TRUE; } /** * Load all the effects for an image style. * * @param $style * An image style array. * @return * An array of effects associated with specified style in the format * array('isid' => array()), or an empty array if the specified style has * no effects. */ function image_style_effects($style) { $effects = image_effects(); $style_effects = array(); foreach ($effects as $effect) { if ($style['isid'] == $effect['isid']) { $style_effects[$effect['ieid']] = $effect; } } return $style_effects; } /** * Get an array of image styles suitable for using as select list options. * * @param $include_empty * If TRUE a option will be inserted in the options array. * @return * Array of image styles both key and value are set to style name. */ function image_style_options($include_empty = TRUE) { $styles = image_styles(); $options = array(); if ($include_empty && !empty($styles)) { $options[''] = t(''); } $options = array_merge($options, drupal_map_assoc(array_keys($styles))); if (empty($options)) { $options[''] = t('No defined styles'); } return $options; } /** * Menu callback; Given a style and image path, generate a derivative. * * This menu callback is always served after checking a token to prevent * generation of unnecessary images. After generating an image transfer it to * the requesting agent via file_transfer(). */ function image_style_generate() { $args = func_get_args(); $style = array_shift($args); $style_name = $style['name']; $path = implode('/', $args); $source = file_create_path($path); $path_md5 = md5($path); $destination = image_style_path($style['name'], $path); // Check that it's a defined style and that access was granted by // image_style_generate_url(). if (!$style || !cache_get('access:' . $style_name . ':' . $path_md5, 'cache_image')) { drupal_access_denied(); exit(); } // Don't start generating the image if it is already in progress. $cid = 'generate:' . $style_name . ':' . $path_md5; if (cache_get($cid, 'cache_image')) { print t('Image generation in progress, please try again shortly.'); exit(); } // If the image has already been generated then send it. if ($image = image_load($destination)) { file_transfer($image->source, array('Content-type: ' . $image->info['mime_type'], 'Content-length: ' . $image->info['file_size'])); } // Set a cache entry designating this image as being in-process. cache_set($cid, $destination, 'cache_image'); // Try to generate the image. if (image_style_create_derivative($style, $source, $destination)) { $image = image_load($destination); cache_clear_all($cid, 'cache_image'); file_transfer($image->source, array('Content-type: ' . $image->info['mime_type'], 'Content-length: ' . $image->info['file_size'])); } else { cache_clear_all($cid, 'cache_image'); watchdog('image', 'Unable to generate the derived image located at %path.', $destination); print t('Error generating image.'); exit(); } } /** * Create a new image based on an image style. * * @param $style * An image style array. * @param $source * Path of the source file. * @param $destination * Path of the destination file. * @return * TRUE if an image derivative is generated, FALSE if no image derivative * is generated. NULL if the derivative is being generated. */ function image_style_create_derivative($style, $source, $destination) { // Get the folder for the final location of this style. $directory = dirname($destination); // Build the destination folder tree if it doesn't already exist. if (!file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { watchdog('image', 'Failed to create style directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR); return FALSE; } if (!$image = image_load($source)) { return FALSE; } foreach ($style['effects'] as $effect) { image_effect_apply($image, $effect); } if (!image_save($image, $destination)) { if (file_exists($destination)) { watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $destination), WATCHDOG_ERROR); } return FALSE; } return TRUE; } /** * Flush cached media for a style. * * @param $style * An image style array. */ function image_style_flush($style) { $style_directory = realpath(file_directory_path() . '/styles/' . $style['name']); if (is_dir($style_directory)) { file_unmanaged_delete_recursive($style_directory); } // Let other modules update as necessary on flush. module_invoke_all('image_style_flush', $style); // Clear image style and effect caches. cache_clear_all('image_styles', 'cache'); cache_clear_all('image_effects', 'cache'); drupal_static_reset('image_styles'); drupal_static_reset('image_effects'); // Clear page caches when flushing. if (module_exists('block')) { cache_clear_all('*', 'cache_block', TRUE); } cache_clear_all('*', 'cache_page', TRUE); } /** * Return the complete URL to an image when using a style. * * If the image has already been created then its location will be returned. If * it does not then image_style_generate_url() will be called. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. * @return * The absolute URL where a style image can be downloaded, suitable for use * in an tag. If the site is using the default method for generating * images, the image may not yet exist and will only be created when a * visitor's browser requests the file. * @see image_style_generate_url() * @see image_style_path() */ function image_style_url($style_name, $path) { $style_path = image_style_path($style_name, $path); if (file_exists($style_path)) { return file_create_url($style_path); } return image_style_generate_url($style_name, $path); } /** * Return the URL for an image derivative given a style and image path. * * This function is the default image generation method. It returns a URL for * an image that can be used in an tag. When the browser requests the * image at image/generate/[style_name]/[path] the image is generated if it does * not already exist and then served to the browser. This allows each image to * have its own PHP instance (and memory limit) for generation of the new image. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. * @return * The absolute URL where a style image can be downloaded, suitable for use * in an tag. Requesting the URL will cause the image to be created. * @see image_style_generate() * @see image_style_url() */ function image_style_generate_url($style_name, $path) { $destination = image_style_path($style_name, $path); // If the image already exists use that rather than regenerating it. if (file_exists($destination)) { return image_style_url($style_name, $path); } // Disable page cache for this request. This prevents anonymous users from // needlessly hitting the image generation URL when the image already exists. $GLOBALS['conf']['cache'] = CACHE_DISABLED; // Set a cache entry to grant access to this style/image path. This will be // checked by image_style_generate(). cache_set('access:' . $style_name . ':' . md5($path), 1, 'cache_image', time() + 600); // Generate a callback path for the image. $url = url('image/generate/' . $style_name . '/' . $path, array('absolute' => TRUE)); return $url; } /** * Return a relative path to an image when using a style. * * The path returned by this function may not exist. The default generation * method only creates images when they are requested by a user's browser. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. * @return * The path to an image style image relative to Drupal's root. * @see image_style_url() */ function image_style_path($style_name, $path) { return file_directory_path() . '/styles/' . $style_name . '/' . file_directory_strip($path); } /** * Pull in effects exposed by other modules using hook_image_effect_info(). * * @return * An array of effects to be used when transforming images. * @see hook_image_effect_info() * @see image_effect_definition_load() */ function image_effect_definitions() { $effects = &drupal_static(__FUNCTION__); if (!isset($effects)) { if ($cache = cache_get('image_effects') && !empty($cache->data)) { $effects = $cache->data; } else { $effects = array(); foreach (module_implements('image_effect_info') as $module) { foreach (module_invoke($module, 'image_effect_info') as $name => $effect) { // Ensure the current toolkit supports the effect. $effect['module'] = $module; $effect['name'] = $name; $effect['data'] = isset($effect['data']) ? $effect['data'] : array(); $effects[$name] = $effect; }; } uasort($effects, '_image_effect_definitions_sort'); cache_set('image_effects', $effects); } } return $effects; } /** * Load the definition for an effect. * * The effect definition is a set of core properties for an effect, not * containing any user-settings. The definition defines various functions to * call when configuring or executing an effect. This loader is mostly for * internal use within image.module. Use image_effect_load() or * image_style_load() to get effects that contain configuration. * * @param $effect * The name of the effect definition to load. * @return * An array containing the image effect definition with the following keys: * - "effect": The unique name for the effect being performed. Usually prefixed * with the name of the module providing the effect. * - "module": The module providing the effect. * - "help": A description of the effect. * - "function": The name of the function that will execute the effect. * - "form": i'm (optional) The name of a function to configure the effect. * - "summary": (optional) The name of a theme function that will display a * one-line summary of the effect. Does not include the "theme_" prefix. */ function image_effect_definition_load($effect) { $definitions = image_effect_definitions(); return isset($definitions[$effect]) ? $definitions[$effect] : FALSE; } /** * Load all image effects from the database. * * @return * An array of all image effects. * @see image_effect_load() */ function image_effects() { $effects = &drupal_static(__FUNCTION__); if (!isset($effects)) { $effects = array(); // Add database image effects. $result = db_select('image_effects', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('image_effects') ->orderBy('image_effects.weight', 'ASC') ->execute(); foreach ($result as $effect) { $effect['data'] = unserialize($effect['data']); $definition = image_effect_definition_load($effect['name']); // Do not load effects whose definition cannot be found. if ($definition) { $effect = array_merge($definition, $effect); $effects[$effect['ieid']] = $effect; } } } return $effects; } /** * Load a single image effect. * * @param $ieid * The image effect ID. * @return * An image effect array, consisting of the following keys: * - "ieid": The unique image effect ID. * - "isid": The unique image style ID that contains this effect. * - "weight": The weight of this effect within the image style. * - "effect": The name of the effect definition that powers this effect. * - "data": An associative array of configuration options for this effect. * Besides these keys, the entirety of the image definition is merged into * the image effect array. Returns FALSE if the specified effect cannot be * found. * @see image_style_load() * @see image_effect_definition_load() */ function image_effect_load($ieid) { $effects = image_effects(); return isset($effects[$ieid]) ? $effects[$ieid] : FALSE; } /** * Save an image effect. * * @param $effect * An image effect array. * @return * An image effect array. In the case of a new effect 'ieid' will be set. */ function image_effect_save($effect) { if (!empty($effect['ieid'])) { drupal_write_record('image_effects', $effect, 'ieid'); } else { drupal_write_record('image_effects', $effect); } $style = image_style_load(NULL, $effect['isid']); image_style_flush($style); return $effect; } /** * Delete an image effect. * * @param $effect * An image effect array. */ function image_effect_delete($effect) { db_delete('image_effects')->condition('ieid', $effect['ieid'])->execute(); $style = image_style_load(NULL, $effect['isid']); image_style_flush($style); } /** * Given an image object and effect, perform the effect on the file. * * @param $image * An image object returned by image_load(). * @param $effect * An image effect array. * @return * TRUE on success. FALSE if unable to perform effect on image. */ function image_effect_apply(&$image, $effect) { if (drupal_function_exists($effect['effect callback'])) { return call_user_func($effect['effect callback'], $image, $effect['data']); } return FALSE; } /** * Return a themed image using a specific image style. * * @param $style_name * The name of the style to be used to alter the original image. * @param $path * The path of the image file relative to the Drupal files directory. * This function does not work with images outside the files directory nor * with remotely hosted images. * @param $alt * The alternative text for text-based browsers. * @param $title * The title text is displayed when the image is hovered in some popular * browsers. * @param $attributes * Associative array of attributes to be placed in the img tag. * @param $getsize * If set to TRUE, the image's dimension are fetched and added as * width/height attributes. * @return * A string containing the image tag. * @ingroup themeable */ function theme_image_style($style_name, $path, $alt = '', $title = '', $attributes = array(), $getsize = TRUE) { // theme_image() can only honor the $getsize parameter with local file paths. // The derivative image is not created until it has been requested so the file // may not yet exist, in this case we just fallback to the URL. $style_path = image_style_path($style_name, $path); if (!file_exists($style_path)) { $style_path = image_style_url($style_name, $path); } return theme('image', $style_path, $alt, $title, $attributes, $getsize); } /** * Accept a percentage and return it in pixels. */ function image_filter_percent($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 a pixel offset. * * @param $value * @param $current_pixels * @param $new_pixels */ function image_filter_keyword($value, $current_pixels, $new_pixels) { switch ($value) { case 'top': case 'left': return 0; case 'bottom': case 'right': return $current_pixels - $new_pixels; case 'center': return $current_pixels / 2 - $new_pixels / 2; } return $value; } /** * Internal function for sorting image effect definitions through uasort(). * * @see image_effect_definitions() */ function _image_effect_definitions_sort($a, $b) { return strcasecmp($a['name'], $b['name']); }