imagecache_external.module in Imagecache External 7.2
Same filename and directory in other branches
Allows the usage of Image Styles on external images.
File
imagecache_external.moduleView source
<?php
/**
* @file
* Allows the usage of Image Styles on external images.
*/
/**
* Implements hook_cron().
*/
function imagecache_external_cron() {
$now = time();
$last_run_timestamp = variable_get('imagecache_external_cron_last_run', 0);
// Run every 24 hours.
if ($now - $last_run_timestamp > 3600 * 24) {
imagecache_external_flush_cache('cron');
variable_set('imagecache_external_cron_last_run', $now);
}
}
/**
* Implements hook_menu().
*/
function imagecache_external_menu() {
$items['admin/config/media/imagecache_external'] = array(
'title' => 'Imagecache External',
'description' => 'Configure imagecache external',
'file' => 'imagecache_external.admin.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'imagecache_external_admin_form',
),
'access arguments' => array(
'administer imagecache external',
),
'type' => MENU_NORMAL_ITEM,
);
$items['admin/config/media/imagecache_external/settings'] = array(
'title' => 'Settings',
'description' => 'Configure imagecache external',
'file' => 'imagecache_external.admin.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'imagecache_external_admin_form',
),
'access arguments' => array(
'administer imagecache external',
),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items['admin/config/media/imagecache_external/flush'] = array(
'title' => 'Flush external images',
'description' => 'Flush external images',
'file' => 'imagecache_external.admin.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'imagecache_external_flush_form',
),
'access arguments' => array(
'administer imagecache external',
),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
return $items;
}
/**
* Implements hook_permission().
*/
function imagecache_external_permission() {
return array(
'administer imagecache external' => array(
'title' => t('Administer Imagecache External'),
'description' => t('Change the Imagecache External settings.'),
),
);
}
/**
* Implements hook_field_formatter_info().
*/
function imagecache_external_field_formatter_info() {
$formatters = array(
'imagecache_external_image' => array(
'label' => t('Imagecache External Image'),
'field types' => array(
'text',
'link_field',
),
'settings' => array(
'imagecache_external_style' => '',
'imagecache_external_link' => '',
),
),
);
return $formatters;
}
/**
* Implements hook_field_formatter_settings_form().
*/
function imagecache_external_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$image_styles = image_style_options(FALSE);
$element['imagecache_external_style'] = array(
'#title' => t('Image style'),
'#type' => 'select',
'#default_value' => $settings['imagecache_external_style'],
'#empty_option' => t('None (original image)'),
'#options' => $image_styles,
);
$link_types = array(
'content' => t('Content'),
'file' => t('File'),
);
$element['imagecache_external_link'] = array(
'#title' => t('Link image to'),
'#type' => 'select',
'#default_value' => $settings['imagecache_external_link'],
'#empty_option' => t('Nothing'),
'#options' => $link_types,
);
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function imagecache_external_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = array();
$image_styles = image_style_options(FALSE);
// Unset possible 'No defined styles' option.
unset($image_styles['']);
// Styles could be lost because of enabled/disabled modules that defines
// their styles in code.
if (isset($image_styles[$settings['imagecache_external_style']])) {
$summary[] = t('Image style: @style', array(
'@style' => $image_styles[$settings['imagecache_external_style']],
));
}
else {
$summary[] = t('Original image');
}
$link_types = array(
'content' => t('Linked to content'),
'file' => t('Linked to file'),
);
// Display this setting only if image is linked.
if (isset($link_types[$settings['imagecache_external_link']])) {
$summary[] = $link_types[$settings['imagecache_external_link']];
}
return implode('<br />', $summary);
}
/**
* Implements hook_field_formatter_view().
*/
function imagecache_external_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
// Check if the formatter involves a link.
if ($display['settings']['imagecache_external_link'] == 'content') {
$uri = entity_uri($entity_type, $entity);
}
elseif ($display['settings']['imagecache_external_link'] == 'file') {
$link_file = TRUE;
}
// Check if the field provides a title.
if ($field['type'] == 'link_field') {
if ($instance['settings']['title'] != 'none') {
$field_title = TRUE;
}
}
foreach ($items as $delta => $item) {
// Set path and alt text.
$image_alt = '';
if ($field['type'] == 'link_field') {
$image_path = imagecache_external_generate_path($item['url']);
// If present, use the Link field title to provide the alt text.
if (isset($field_title)) {
// The link field appends the url as title when the title is empty.
// We don't want the url in the alt tag, so let's check this.
if ($item['title'] != $item['url']) {
$image_alt = isset($field_title) ? $item['title'] : '';
}
}
}
else {
$image_path = imagecache_external_generate_path($item['value']);
}
$image_info = image_get_info($image_path);
$image_item = array(
'uri' => $image_path,
'width' => $image_info['width'],
'height' => $image_info['height'],
'alt' => $image_alt,
'title' => '',
);
if (isset($link_file)) {
$uri = array(
'path' => file_create_url($image_path),
'options' => array(),
);
}
$element[$delta] = array(
'#theme' => 'image_formatter',
'#item' => $image_item,
'#image_style' => $display['settings']['imagecache_external_style'],
'#path' => isset($uri) ? $uri : '',
);
}
return $element;
}
/**
* Implements hook_theme().
*/
function imagecache_external_theme() {
return array(
// Theme functions in image.module.
'imagecache_external' => array(
'variables' => array(
'style_name' => NULL,
'path' => NULL,
'alt' => '',
'title' => NULL,
'attributes' => array(),
),
),
);
}
/**
* Returns HTML for an image using a specific image style.
*
* @param array $variables
* An associative array containing:
* - style_name: The name of the style to be used to alter the original image.
* - 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.
* - alt: The alternative text for text-based browsers.
* - title: The title text is displayed when the image is hovered in some
* popular browsers.
* - attributes: Associative array of attributes to be placed in the img tag.
*
* @ingroup themeable
*/
function theme_imagecache_external($variables) {
if ($variables['path'] = imagecache_external_generate_path($variables['path'])) {
return theme('image_style', $variables);
}
return FALSE;
}
/**
* Util to generate a path to an image.
*
* @param string $url
* The url to the image.
* @param bool $force_refresh
* Whether to refresh the cached image.
*
* @return string
* The url to the image.
*/
function imagecache_external_generate_path($url, $force_refresh = FALSE) {
// Create the extenal images directory and ensure it's writable.
$hash = md5($url);
$filename = $hash;
// Check if this is a non-standard file stream and adjust accordingly.
$scheme = file_uri_scheme($url);
if ($scheme != 'http' && $scheme != 'https') {
// Obtain the external URL to this file.
$url = file_create_url($url);
}
// Parse the url to get the path components.
$url_parameters = drupal_parse_url($url);
// Add the extension for real images.
if ($extension = strtolower(pathinfo($url_parameters['path'], PATHINFO_EXTENSION))) {
if (in_array($extension, array(
'jpg',
'png',
'gif',
'jpeg',
))) {
$filename .= '.' . $extension;
}
}
else {
// Use jpg as default extension.
$filename .= variable_get('imagecache_default_extension', '.jpg');
}
$default_scheme = file_default_scheme();
$directory = $default_scheme . '://' . variable_get('imagecache_directory', 'externals');
// Allow other modules to change the directory.
drupal_alter('imagecache_external_directory', $directory, $filename, $url);
if (file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
$needs_refresh = FALSE;
$filepath = $directory . '/' . $filename;
// Allow modules to add custom logic to check if it needs to be re-fetched.
drupal_alter('imagecache_external_needs_refresh', $needs_refresh, $filepath);
if ($needs_refresh === FALSE && $force_refresh === FALSE) {
return $filepath;
}
elseif ($filepath = imagecache_external_fetch($url, $directory . '/' . $filename)) {
return $filepath;
}
}
// We couldn't get the file.
return FALSE;
}
/**
* Implements hook_imagecache_external_needs_refresh_alter().
*/
function imagecache_external_imagecache_external_needs_refresh_alter(&$needs_refresh, $filepath) {
if (!file_exists($filepath)) {
$needs_refresh = TRUE;
}
}
/**
* Api function to fetch a url.
*
* @param string $url
* The url to fetch.
* @param string $cachepath
* The directory where to save the images within the files directory.
*/
function imagecache_external_fetch($url, $cachepath) {
// Validate the image URL against the whitelist.
if (imagecache_external_validate_host($url) === FALSE) {
return FALSE;
}
$result = drupal_http_request($url);
$code = floor($result->code / 100) * 100;
$types = imagecache_external_allowed_mimetypes();
// If content-type not set, use the default 'application/octet-stream'.
$response_mimetype = !empty($result->headers['content-type']) ? strtolower($result->headers['content-type']) : 'application/octet-stream';
// Add extension to the cached file to allow file_entity to use it for
// mimetype identification.
$cachepath_ext = pathinfo($cachepath, PATHINFO_EXTENSION);
if (!$cachepath_ext && variable_get('imagecache_default_extension', '.jpg') != '') {
require_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
$map = file_mimetype_mapping();
$mimetype_id = array_search($response_mimetype, $map['mimetypes']);
if ($mimetype_id !== FALSE) {
$cachepath_ext = array_search($mimetype_id, $map['extensions']);
$cachepath .= $cachepath_ext ? '.' . $cachepath_ext : '';
}
}
$fallback_image_fid = variable_get('imagecache_fallback_image', '');
// Explode content-type to handle mimetypes with more than one
// property (eg. image/jpeg;charset=UTF-8).
$content_type_array = explode(';', $response_mimetype);
$content_type_allowed = FALSE;
foreach ($content_type_array as $content_type) {
if (in_array(strtolower($content_type), $types)) {
$content_type_allowed = TRUE;
}
}
if (!empty($result->data) && $code != 400 && $code != 500 && $content_type_allowed) {
if (variable_get('imagecache_external_management', 'unmanaged') == 'unmanaged') {
return file_unmanaged_save_data($result->data, $cachepath, FILE_EXISTS_REPLACE);
}
else {
$file = file_save_data($result->data, $cachepath, FILE_EXISTS_REPLACE);
return $file->uri;
}
}
elseif (!empty($fallback_image_fid)) {
$fallback_image = file_load($fallback_image_fid);
return $fallback_image->uri;
}
else {
// If we are unsuccessful then log a message in watchdog.
watchdog('imagecache_external', 'The image %url could not be retrieved', array(
'%url' => $url,
));
return FALSE;
}
}
/**
* Helper function to validate the image host against the whitelist.
*
* @param string $url
* The URL of the image.
*
* @return boolean
* Can the image be fetched or not?
*/
function imagecache_external_validate_host($url) {
// Extract the hostname from the url.
if (!($host = parse_url($url, PHP_URL_HOST))) {
return FALSE;
}
// Check if a whitelist is used and if the host is in the list.
if (variable_get('imagecache_external_use_whitelist', TRUE)) {
$list = preg_split('/\\s+/', variable_get('imagecache_external_hosts', ''));
$validhost = FALSE;
foreach ($list as $ext_host) {
if (preg_match('/\\.?' . preg_quote($ext_host, '/') . '/', $host) === 1) {
$validhost = TRUE;
break;
}
}
if (!$validhost) {
// If we are unsuccessful then log a message in watchdog.
watchdog('imagecache_external', 'The image %url could not be retrieved, it did not meet the whitelist requirements.', array(
'%url' => $url,
));
return FALSE;
}
}
return TRUE;
}
/**
* Implements hook_module_implements_alter().
*
* Because the Image module already checks for the image style paths,
* and returns and access_denied() for Imagecache External images,
* we need to override this function and do the check ourselves.
*/
function imagecache_external_module_implements_alter(&$implementations, $hook) {
if ($hook == 'file_download') {
unset($implementations['image']);
}
}
/**
* Implements hook_file_download().
*
* When using the private file system, we have to let Drupal know it's OK to
* download images from our Imagecache External directory.
*
* @return array
* An array keyed with HTTP Headers.
*/
function imagecache_external_file_download($uri) {
// Check if the path contains 'imagecache/external'.
// If not, we fallback to the Image module.
if (strpos($uri, '/' . variable_get('imagecache_directory', 'externals') . '/') > 0) {
$info = image_get_info($uri);
// For safety, we only allow our own mimetypes.
if (in_array($info['mime_type'], imagecache_external_allowed_mimetypes())) {
return array(
'Content-Type' => $info['mime_type'],
'Content-Length' => $info['file_size'],
);
}
}
else {
// Do a fallback to the Image module.
return image_file_download($uri);
}
}
/**
* Helper function to flush caches.
*
* @param string $mode
* The mode to call the cache flush in. Two options:
* - 'all' will flush all items in the cache (default).
* - 'cron' will flush only items older than the cron threshold.
*
* @return boolean
* A Boolean value to indicate that the operation succeeded.
*/
function imagecache_external_flush_cache($mode = 'all') {
$success = FALSE;
$path = file_build_uri(variable_get('imagecache_directory', 'externals'));
switch ($mode) {
case 'cron':
$threshold = (int) variable_get('imagecache_external_cron_flush_threshold', '');
// If a threshold is not set, don't flush the cache.
if ($threshold <= 0) {
break;
}
$now = time();
$removed_count = 0;
$external_cache_files = file_scan_directory($path, '/.+\\.(jpe?g|gif|png)$/i');
foreach ($external_cache_files as $external_cache_file) {
$uri = $external_cache_file->uri;
$stat = stat($uri);
if ($now - $stat['mtime'] > $threshold * 3600 * 24) {
if (drupal_unlink($uri)) {
$removed_count++;
}
else {
watchdog('imagecache_external', 'Could not remove cached external file @file', array(
'@file' => $uri,
), WATCHDOG_WARNING);
}
}
}
if ($removed_count > 0) {
watchdog('imagecache_external', '@count files removed from the external image cache', array(
'@count' => $removed_count,
));
}
// This is always considered successful.
$success = TRUE;
break;
case 'all':
default:
if (is_dir($path)) {
if (file_unmanaged_delete_recursive($path)) {
watchdog('imagecache_external', 'Imagecache caches have been flushed');
$success = TRUE;
}
}
break;
}
return $success;
}
/**
* Helper function that returns allowed mimetypes for external caching.
*
* @return array
* The allowed mimetypes.
*/
function imagecache_external_allowed_mimetypes() {
return variable_get('imagecache_external_allowed_mimetypes', array(
'image/jpg',
'image/jpg;charset=utf-8',
'image/jpeg',
'image/jpeg;charset=utf-8',
'image/png',
'image/png;charset=utf-8',
'image/gif',
'image/gif;charset=utf-8',
'application/octet-stream',
'application/octet-stream;charset=utf-8',
'binary/octet-stream',
));
}
/**
* Implements hook_stage_file_proxy_excluded_paths_alter().
*
* Prevent the Stage File Proxy module, if exists, to fetch external images.
*/
function imagecache_external_stage_file_proxy_excluded_paths_alter(array &$excluded_paths, $uri) {
$excluded_paths[] = '/' . variable_get('imagecache_directory', 'externals') . '/';
}