You are here

imageinfo_cache.module in Imageinfo Cache 6.2

Same filename and directory in other branches
  1. 6 imageinfo_cache.module
  2. 7.3 imageinfo_cache.module

Cache image info for theme_imagecache & theme_imagefield_image.

File

imageinfo_cache.module
View source
<?php

/**
 * @file
 *   Cache image info for theme_imagecache & theme_imagefield_image.
 */

/**
 * Store imageinfo cached data for 3 days.
 */
define('IMAGEINFO_CACHE_LIFETIME', 60 * 60 * 72);

/**
 * Orginal imagecache theme function name.
 */
define('IMAGEINFO_CACHE_THEME_IMAGECACHE_CALLBACK', 'theme_imagecache');

/**
 * Orginal imagefield_image theme function name.
 */
define('IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE_CALLBACK', 'theme_imagefield_image');

/**
 * Use theme_imagecache override.
 */
define('IMAGEINFO_CACHE_THEME_IMAGECACHE', TRUE);

/**
 * Use theme_imagefield_image override.
 */
define('IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE', TRUE);

/**
 * Pre-generate imagecache images.
 */
define('IMAGEINFO_CACHE_IMAGECACHE_PREGENERATE', TRUE);

/**
 * Maximum number of images to process per async request.
 */
define('IMAGEINFO_CACHE_ASYNC_MAX', 5);

/**
 * Set the http mode. Non Blocking = 0; Blocking = 1.
 */
define('IMAGEINFO_CACHE_HTTPRL_MODE', 0);

/**
 * Generate all presets by default.
 */
define('IMAGEINFO_CACHE_CCK_WIDGET', TRUE);

/**
 * Implements hook_form_alter().
 */
function imageinfo_cache_form_alter(&$form, $form_state, $form_id) {

  // Make sure form is a filefiled using the imagefield widget
  if (empty($form['basic']['type']['#value']) || $form['basic']['type']['#value'] != 'filefield' || empty($form['widget_module']['#value']) || $form['widget_module']['#value'] != 'imagefield') {
    return;
  }

  // Call function in imageinfo_cache.admin.inc
  module_load_include('inc', 'imageinfo_cache', 'imageinfo_cache.admin');
  return imageinfo_cache_cck_widget_form($form, $form_state, $form_id);
}

/**
 * Implements hook_menu().
 */
function imageinfo_cache_menu() {
  $items = array();
  $items['imageinfo_cache_generate'] = array(
    'page callback' => 'imageinfo_cache_primer',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );
  $items['admin/settings/imageinfo-cache'] = array(
    'title' => 'Imageinfo Cache',
    'description' => 'Configuration page for Imageinfo Cache module.',
    'page callback' => 'imageinfo_cache_admin_page',
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'imageinfo_cache.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_file_insert().
 *
 * Grab the file right after it has been uploaded via filefield.
 *
 * @param $file
 *   object File info
 */
function imageinfo_cache_file_insert($file) {

  // Make sure file has the correct info an it is an image.
  $op = 'insert';
  if (imageinfo_cache_check_file($file, $op)) {
    imageinfo_cache_shutdown_async($file, $op, 'widget');
  }
}

/**
 * Implements hook_file_delete().
 *
 * Grab files right before it gets deleted.
 *
 * @param $file
 *   object File info
 */
function imageinfo_cache_file_delete($file) {

  // Make sure file has the correct info an it is an image.
  $op = 'delete';
  if (imageinfo_cache_check_file($file, $op)) {
    imageinfo_cache_shutdown_async($file, $op, 'widget');
  }
}

/**
 * Implements hook_nodeapi().
 *
 * Grab files after being saved to node object
 */
function imageinfo_cache_nodeapi(&$node, $op) {
  if ($op == 'insert' || $op == 'update') {
    $op = 'insert';
    $files = imageinfo_cache_get_images_in_node($node);
    foreach ($files as $file) {
      $file = (object) $file;
      if (imageinfo_cache_check_file($file, $op)) {
        imageinfo_cache_shutdown_async($file, $op, 'nodeapi');
      }
    }
  }
}

/**
 * Function that gets called right after a file has been uploaded.
 *
 * @param $file
 *   object File info
 * @param $op
 *   string insert or delete
 */
function imageinfo_cache_shutdown_async($file = NULL, $op = NULL, $state = NULL) {
  global $base_path, $base_root;
  static $files = array();
  static $registered = FALSE;
  static $array_counter = 0;
  static $file_counter = 0;

  // Record file and op in static array & register shutdown function if needed.
  if (!empty($file) && !empty($op)) {

    // Make sure file has the correct info and it is an image.
    if (imageinfo_cache_check_file($file, $op) == FALSE) {
      return;
    }

    // Only send if file is missing from any one of the caches
    $missing = FALSE;
    $cid = 'theme_imagefield_' . md5($file->filepath);
    $cache = cache_get($cid, 'cache_imageinfo');
    if (empty($cache)) {
      $missing = TRUE;
    }

    // Build file info array.
    $file_info = array(
      'fid' => $file->fid,
      'filepath' => $file->filepath,
      'timestamp' => $file->timestamp,
      'op' => $op,
      'state' => $state,
    );
    if (!empty($file->field['type_name'])) {
      $file_info['field']['type_name'] = $file->field['type_name'];
    }
    if (!empty($file->field['field_name'])) {
      $file_info['field']['field_name'] = $file->field['field_name'];
    }

    // Do imagecache presets.
    if (!$missing && module_exists('imagecache') && function_exists('imagecache_generate_image') && variable_get('imageinfo_cache_imagecache_pregenerate', IMAGEINFO_CACHE_IMAGECACHE_PREGENERATE)) {

      // Check each imagecache preset
      foreach (imageinfo_cache_presets_to_generate($file_info) as $preset) {

        // see if a cache entry exists for that preset.
        $cid = 'imagecache_' . $preset . '_' . md5($file->filepath);
        $cache = cache_get($cid, 'cache_imageinfo');
        if (empty($cache)) {
          $missing = TRUE;
          break;
        }
      }
    }

    // All data for this image is cached for insert.
    // None of the data for this image is cached for delete.
    if (!$missing && $op == 'insert') {
      return;
    }

    // Only send 5 files at a time.
    $file_counter++;
    if ($file_counter > (int) variable_get('imageinfo_cache_async_max', IMAGEINFO_CACHE_ASYNC_MAX)) {
      $array_counter++;
      $file_counter = 0;
    }

    // Add info to static array.
    $files[$array_counter][$file->fid] = $file_info;
    if (!$registered) {
      register_shutdown_function(__FUNCTION__);
      $registered = TRUE;
    }
    return;
  }

  // Code below runs on shutdown.
  // Exit function if we have nothing to do.
  if (empty($files)) {
    return;
  }

  // URL key.
  $key = variable_get('imageinfo_cache_url_key', md5(drupal_get_private_key()));
  foreach ($files as $values) {
    $query['files'] = $values;
    $query['key'] = $key;

    // Setup request URL and headers.
    $query_string = http_build_query($query, '', '&');
    $ip = variable_get('imageinfo_cache_server_addr', FALSE);
    if (!empty($ip)) {
      $url = 'http://' . $ip . $base_path . 'imageinfo_cache_generate';
    }
    else {
      $url = httprl_build_url_self('imageinfo_cache_generate');
    }
    $headers = array(
      'Host' => $_SERVER['HTTP_HOST'],
      'Content-Type' => 'application/x-www-form-urlencoded',
    );
    $options = array(
      'headers' => $headers,
      'method' => 'POST',
      'data' => $query_string,
      'blocking' => variable_get('imageinfo_cache_httprl_mode', IMAGEINFO_CACHE_HTTPRL_MODE),
    );

    // Create imagecache presets generation requests.
    httprl_request($url, $options);
  }

  // Execute requests in parallel.
  $resposes = httprl_send_request();

  // If blocking then check the respose back
  if (variable_get('imageinfo_cache_httprl_mode', IMAGEINFO_CACHE_HTTPRL_MODE)) {
    foreach ($resposes as $url => $info) {

      // If async failed; block here and generate images and caches.
      if (strcmp(trim($info->data), $key) !== 0) {
        watchdog('imageinfo_cache', 'Asynchronous imageinfo cache primer failed %url. Using Synchronous mode. %key <br /> !data', array(
          '%url' => $url,
          '%key' => $key . ' ' . $info->data . ' ' . strcmp(trim($info->data), $key),
          '!data' => print_r($info, TRUE),
        ));
        imageinfo_cache_primer($query);
      }
    }
  }
}

/**
 * Implements hook_theme_registry_alter().
 */
function imageinfo_cache_theme_registry_alter(&$theme_registry) {

  // Modify the imagecache theme callback.
  if (variable_get('imageinfo_cache_theme_imagecache', IMAGEINFO_CACHE_THEME_IMAGECACHE) && isset($theme_registry['imagecache'])) {

    // Get old theme registry function call.
    variable_set('imageinfo_cache_theme_imagecache_callback', $theme_registry['imagecache']['function']);

    // Set it to our new function that includes caching.
    $theme_registry['imagecache']['function'] = 'imageinfo_cache_theme_imagecache';
  }

  // Modify the imagefield_image theme callback.
  if (variable_get('imageinfo_cache_theme_imagefield_image', IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE) && isset($theme_registry['imagefield_image'])) {

    // Get old theme registry function call.
    variable_set('imageinfo_cache_theme_imagefield_image_callback', $theme_registry['imagefield_image']['function']);

    // Set it to our new function that includes caching.
    $theme_registry['imagefield_image']['function'] = 'imageinfo_cache_theme_imagefield_image';
  }
}

/**
 * Create an image tag for an imagecache derivative
 *
 * @param $presetname
 *   String with the name of the preset used to generate the derivative image.
 * @param $path
 *   String path to the original image you wish to create a derivative image
 *   tag for.
 * @param $alt
 *   Optional string with alternate text for the img element.
 * @param $title
 *   Optional string with title for the img element.
 * @param $attributes
 *   Optional drupal_attributes() array. If $attributes is an array then the
 *   default imagecache classes will not be set automatically, you must do this
 *   manually.
 * @param $getsize
 *   If set to TRUE, the image's dimension are fetched and added as width/height
 *   attributes.
 * @param $absolute
 * A Boolean indicating that the URL should be absolute. Defaults to TRUE.
 *
 * @return
 *   HTML img element string.
 */
function imageinfo_cache_theme_imagecache($presetname, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE, $absolute = TRUE) {

  // Only run if we are going to get the width and height from the file.
  if ($getsize) {
    $imagecache_path = imagecache_create_path($presetname, $path);
    if ($imagecache_path) {

      // Cache ID and cache_get.
      $cid = 'imagecache_' . $presetname . '_' . md5($path);
      $image = cache_get($cid, 'cache_imageinfo');

      // Use cached data if available.
      if (!empty($image)) {
        $image = $image->data;
        if (empty($image) || empty($image['width']) || empty($image['height'])) {
          unset($image);
        }
        if ($image['width'] == 1 || $image['height'] == 1) {
          unset($image);
        }
      }

      // Get the width and height from the file if $image was bad.
      if (empty($image)) {
        $image = image_get_info($imagecache_path);
        if (!empty($image) && !empty($image['width']) && !empty($image['height'])) {
          $lifetime = variable_get('imageinfo_cache_lifetime', IMAGEINFO_CACHE_LIFETIME);
          cache_set($cid, $image, 'cache_imageinfo', time() + $lifetime);
        }
      }

      // If we have the image info then set some variables before calling the
      // original theme function.
      if (!empty($image) && !empty($image['width']) && !empty($image['height'])) {

        // Check is_null() so people can intentionally pass an empty array of
        // to override the defaults completely.
        if (is_null($attributes)) {
          $attributes = array(
            'class' => 'imagecache imagecache-' . $presetname,
          );
        }
        $attributes['width'] = $image['width'];
        $attributes['height'] = $image['height'];
        $getsize = FALSE;
      }
    }
  }

  // Run original theme function.
  $function = variable_get('imageinfo_cache_theme_imagecache_callback', IMAGEINFO_CACHE_THEME_IMAGECACHE_CALLBACK);
  return $function($presetname, $path, $alt, $title, $attributes, $getsize, $absolute);
}

/**
 * Caches the image dimensions; passes dimensions to the orginal theme function.
 *
 * @see theme_imagefield_image()
 */
function imageinfo_cache_theme_imagefield_image($file, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
  $file = (array) $file;
  if ($getsize) {

    // Use cached width and height if available.
    if (!empty($file['data']['width']) && !empty($file['data']['height'])) {
      $attributes['width'] = $file['data']['width'];
      $attributes['height'] = $file['data']['height'];
      $getsize = FALSE;
    }
    else {

      // Cache ID and cache_get.
      $cid = 'theme_imagefield_' . md5($file['filepath']);
      $image = cache_get($cid, 'cache_imageinfo');

      // Use cached data if available.
      if (!empty($image)) {
        $image = $image->data;
        if (empty($image) || empty($image['width']) || empty($image['height'])) {
          unset($image);
        }
        if ($image['width'] == 1 || $image['height'] == 1) {
          unset($image);
        }
      }

      // Get the width and height from the file if $image was bad.
      if (empty($image)) {
        $image = @getimagesize($file['filepath']);
        if (!empty($image)) {
          $image['width'] = $image[0];
          $image['height'] = $image[1];
          $lifetime = variable_get('imageinfo_cache_lifetime', IMAGEINFO_CACHE_LIFETIME);
          cache_set($cid, $image, 'cache_imageinfo', time() + $lifetime);
        }
      }

      // Set the width and height.
      if (!empty($image) && !empty($image['width']) && !empty($image['height'])) {
        $attributes['width'] = $image['width'];
        $attributes['height'] = $image['height'];
        $getsize = FALSE;
      }
    }
  }

  // Run original theme function.
  $function = variable_get('imageinfo_cache_theme_imagefield_image_callback', IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE_CALLBACK);
  return $function($file, $alt, $title, $attributes, $getsize);
}

/**
 * Run various theme functions so the cache is primed.
 *
 * @param $values
 *   object File info
 * @param $op
 *   string insert or delete
 */
function imageinfo_cache_primer($values = NULL) {

  //    watchdog('debug', str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(print_r($values, TRUE) . print_r($_REQUEST, TRUE)))));
  $async = FALSE;

  // Exit if key does not match & called with $file not set.
  if (is_null($values)) {
    if (empty($_POST['key']) || empty($_POST['files'])) {
      return;
    }
    $key = variable_get('imageinfo_cache_url_key', md5(drupal_get_private_key()));
    if ($key != $_POST['key']) {
      return;
    }
    else {

      // Break connection so processing is async. Return key given to calling url.
      httprl_background_processing(check_plain($_POST['key']));
      $async = TRUE;
    }
    $values = array();

    // $_POST['files'] is trusted because the key matches.
    $values['files'] = $_POST['files'];
  }

  // Process every file.
  foreach ($values['files'] as $file) {
    $file = (object) $file;

    // Skip if we do not have a file ID or filepath.
    if (empty($file->fid) || !is_numeric($file->fid) || empty($file->filepath)) {
      continue;
    }
    if ($file->op == 'insert') {

      // If node type given, match it.
      $node_types = node_get_types();
      if (!empty($file->field['type_name']) && empty($node_types[$file->field['type_name']])) {
        continue;
      }

      // If cck field given, match it.
      if (module_exists('content')) {
        $content_types = content_types();
        if (!empty($file->field['field_name']) && empty($content_types[$file->field['type_name']]['fields'][$file->field['field_name']])) {
          continue;
        }
      }

      // If filefiled_paths module exists, check to see if there are settings for it.
      // If there are filefiled_paths settings, then do not process.
      if (module_exists('filefield_paths') && !empty($file->field['field_name']) && !empty($file->field['field_name']) && $file->state == 'widget' && db_result(db_query("SELECT TRUE FROM {filefield_paths} WHERE type = '%s' AND field = '%s'", $file->field['type_name'], $file->field['field_name']))) {
        continue;
      }

      // Prime imagefield theme cache.
      theme('imagefield_image', (array) $file);

      // Do imagecache presets.
      if (module_exists('imagecache') && function_exists('imagecache_generate_image') && variable_get('imageinfo_cache_imagecache_pregenerate', IMAGEINFO_CACHE_IMAGECACHE_PREGENERATE)) {
        foreach (imageinfo_cache_presets_to_generate($file) as $preset) {

          // Generate each imagecache preset.
          imagecache_generate_image($preset, $file->filepath);

          // For each imagecache preset, Prime the imagecache theme cache.
          theme('imagecache', $preset, $file->filepath);
        }
      }
    }
    elseif ($file->op == 'delete') {

      // Clear the imagefield cache.
      $cid = 'theme_imagefield_' . md5($file->filepath);
      cache_clear_all($cid, 'cache_imageinfo');

      // Do imagecache presets.
      if (module_exists('imagecache') && function_exists('imagecache_generate_image')) {

        // Remove the files.
        imagecache_image_flush($file->filepath);

        // Clear the imagecache cache.
        foreach (imageinfo_cache_presets_to_generate() as $preset) {

          // Remove the imagecache cache object.
          $cid = 'imagecache_' . $preset . '_' . md5($file->filepath);
          cache_clear_all($cid, 'cache_imageinfo');
        }
      }
    }
    else {
      watchdog('imageinfo_cache', 'No operation was matched. ' . $file->op . print_r($file, TRUE));
    }
  }
  if ($async) {

    // Do not start a session for this request.
    exit;
  }
  else {
    return;
  }
}

/**
 * Given a file object, get the imagecache presets to auto generate for it.
 *
 * @param $file
 *   object File info
 * @param $op
 *   string insert or delete
 */
function imageinfo_cache_presets_to_generate($file = NULL) {
  $presets = array();
  $all = TRUE;
  if (!is_null($file) && is_array($file)) {
    $file = (object) $file;
  }

  // Check the node type and CCK field name.
  if (!is_null($file) && !empty($file->field['type_name']) && !empty($file->field['field_name'])) {
    $field_settings = variable_get('imageinfo_cache_cck_widget_' . $file->field['type_name'] . '_' . $file->field['field_name'], IMAGEINFO_CACHE_CCK_WIDGET);
    if (empty($field_settings)) {
      return array();
    }
    $all = FALSE;
    if (!is_array($field_settings) && $field_settings == TRUE) {
      $all = TRUE;
    }
    if (!$all) {
      foreach (imagecache_presets() as $preset) {

        // Get preset name.
        if (!empty($field_settings[$preset['presetid']])) {
          $presets[] = $preset['presetname'];
        }
      }
    }
  }
  if ($all) {
    foreach (imagecache_presets() as $preset) {

      // Get preset name.
      $presets[] = $preset['presetname'];
    }
  }
  return $presets;
}

/**
 * Implements hook_imagecache_preset_flush().
 */
function imageinfo_cache_imagecache_preset_flush($presetdir, $preset) {
  $cid = 'imagecache_' . $preset['presetname'];
  cache_clear_all($cid, 'cache_imageinfo', TRUE);
}

/**
 * Implements hook_imagecache_image_flush().
 */
function imageinfo_cache_imagecache_image_flush($filepath, $preset, $path) {
  $cid = 'imagecache_' . $preset['presetname'] . md5($path);
  cache_clear_all($cid, 'cache_imageinfo');
}

/**
 * Check that the file object has everything we need.
 *
 * @param $file
 *   object File info
 * @param $op
 *   string insert or delete
 */
function imageinfo_cache_check_file($file, $op) {

  // Make sure file has the correct info an it is an image.
  if ($op == 'insert') {
    if (is_object($file) && !empty($file->fid) && !empty($file->field['type_name']) && !empty($file->field['field_name']) && !empty($file->filepath) && strpos($file->filemime, 'image') !== FALSE) {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }
  elseif ($op == 'delete') {
    if (is_object($file) && !empty($file->fid) && !empty($file->filepath) && strpos($file->filemime, 'image') !== FALSE) {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }
}

/**
 * Given a node, get all files associated with it.
 *
 * Currently this only works with images stored in filefields.
 *
 * @param $node
 *   Node object.
 * @return
 *   An array of info from the files table.
 */
function imageinfo_cache_get_node_files($node) {
  $fields = filefield_get_field_list($node->type);
  $files = array();

  // Get the file rows.
  foreach ($fields as $field) {
    $db_info = content_database_info($field);
    $fields = 'f.*';
    $fields .= ', c.' . $db_info['columns']['list']['column'] . ' AS list';
    $fields .= ', c.' . $db_info['columns']['data']['column'] . ' AS data';
    $sql = 'SELECT ' . $fields . ' FROM {files} f INNER JOIN {' . $db_info['table'] . '} c ON f.fid = c.' . $db_info['columns']['fid']['column'] . ' AND c.vid = %d';
    $result = db_query($sql, $node->vid);
    while ($file = db_fetch_array($result)) {
      $file['data'] = unserialize($file['data']);
      $file['field']['field_name'] = $field['field_name'];
      $file['field']['type_name'] = $field['type_name'];
      $files[$file['fid']] = $file;
    }
  }
  return $files;
}

/**
 * Given a node, get all images associated with it.
 *
 * Currently this only works with images stored in filefields.
 *
 * @param $node
 *   Node object.
 * @return
 *   An array of info from the files table.
 */
function imageinfo_cache_get_images_in_node(&$node) {
  $files = array();
  if (module_exists('filefield')) {
    $data = imageinfo_cache_get_node_files($node);
    foreach ($data as $key => $value) {
      if (strpos($value['filemime'], 'image') !== FALSE) {
        $files[$key] = $value;
      }
    }
  }
  return $files;
}

Functions

Namesort descending Description
imageinfo_cache_check_file Check that the file object has everything we need.
imageinfo_cache_file_delete Implements hook_file_delete().
imageinfo_cache_file_insert Implements hook_file_insert().
imageinfo_cache_form_alter Implements hook_form_alter().
imageinfo_cache_get_images_in_node Given a node, get all images associated with it.
imageinfo_cache_get_node_files Given a node, get all files associated with it.
imageinfo_cache_imagecache_image_flush Implements hook_imagecache_image_flush().
imageinfo_cache_imagecache_preset_flush Implements hook_imagecache_preset_flush().
imageinfo_cache_menu Implements hook_menu().
imageinfo_cache_nodeapi Implements hook_nodeapi().
imageinfo_cache_presets_to_generate Given a file object, get the imagecache presets to auto generate for it.
imageinfo_cache_primer Run various theme functions so the cache is primed.
imageinfo_cache_shutdown_async Function that gets called right after a file has been uploaded.
imageinfo_cache_theme_imagecache Create an image tag for an imagecache derivative
imageinfo_cache_theme_imagefield_image Caches the image dimensions; passes dimensions to the orginal theme function.
imageinfo_cache_theme_registry_alter Implements hook_theme_registry_alter().

Constants

Namesort descending Description
IMAGEINFO_CACHE_ASYNC_MAX Maximum number of images to process per async request.
IMAGEINFO_CACHE_CCK_WIDGET Generate all presets by default.
IMAGEINFO_CACHE_HTTPRL_MODE Set the http mode. Non Blocking = 0; Blocking = 1.
IMAGEINFO_CACHE_IMAGECACHE_PREGENERATE Pre-generate imagecache images.
IMAGEINFO_CACHE_LIFETIME Store imageinfo cached data for 3 days.
IMAGEINFO_CACHE_THEME_IMAGECACHE Use theme_imagecache override.
IMAGEINFO_CACHE_THEME_IMAGECACHE_CALLBACK Orginal imagecache theme function name.
IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE Use theme_imagefield_image override.
IMAGEINFO_CACHE_THEME_IMAGEFIELD_IMAGE_CALLBACK Orginal imagefield_image theme function name.