Skip to content
imagecache.module 16.5 KiB
Newer Older
<?php
/* 
  imagecache.module - dynamic image resizer and cache

  imagecache allows you to setup specialized identifiers for dynamic image processing
  and caching. It abuses the rewrite rules used for drupal's clean urls to provide
  Eariler security flaws were overcome by using 'rulesets' similar to  image.module. 
  We don't fuss with the database, and creating data that we have to keep up with in drupal.
  We do it on the fly and flush it when we want to, or when a ruleset changes.
  $get[q] = files/imagecache/<ruleset>/path
	@todo: improve image resizing, reduce distortion.
  @todo: add watermarking capabilities.
  @todo: split action/handlers into their own little .inc files.
  @todo: enforce permissions.


  **
    to add a new handler, 
      add the button & fields in the _imagecache_actions_form
      add a switch to add it in imagecache_admin_list
      add handling code to imagecache_cache

  **

*/

function imagecache_help($section) {
  switch($section) {
    case 'admin/modules#description': return t('enable dynamic image manipulator');
function imagecache_perm() {
  return array('administer imagecache','flush imagecache');
}


function imagecache_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array( 'path' => 'files/imagecache', 
                      'callback' => 'imagecache_cache',
                      'access' => TRUE,
                      'type' => MENU_CALLBACK
               );     
    $items[] = array( 'path' => 'admin/imagecache', 
                      'access' => user_access('administer imagecache'),
                      'callback' => 'imagecache_admin_list',
               );
  }
  return $items;
}


function imagecache_cache() {
  $args = func_get_args();
  $ruleset = array_shift($args);

  $ruleset = _imagecache_ruleset_load_by_name($ruleset);
  $actions = _imagecache_actions_get_by_rulesetid($ruleset['rulesetid']);

  $path = implode('/', $args); 
  // verify that the source exists, if not exit. Maybe display a missing image.
  $source = file_create_path($path);
  if (!is_file($source)) { drupal_set_message('$source does not exist'); 
     return 'source does not exist';
    //deliver not found image and exit.
  }


  $destination = file_create_path() . '/imagecache/'. $ruleset['rulesetname'] .'/'. $path;
  $tmpdestination = file_directory_temp() .'/'. str_replace(dirname($path), '', $path);
  $dir = dirname($destination);
  drupal_set_message('$tmpdestination: '. $tmpdestination);
  drupal_set_message('$destination: '. $destination);
  if(!file_check_directory($dir)) {
    $folders = explode("/",$dir);
    $path = array();
   
    foreach($folders as $folder) {
      $tpath[] = $folder;
      if(!file_check_directory(implode("/", $tpath))) {
        mkdir(implode("/", $tpath));
      }
  }

  if(!file_check_directory($dir)) {
    return 'could not create destination: '. $destination;
  }

  //check if file exists to prevent multiple apache children from trying to generate.
  if (!is_file($tmpdestination)) {
    $generated = TRUE;
    file_copy($source, $tmpdestination);

    foreach($actions as $action) {
      switch($action['data']['function']) {
        case 'resize':
          if (!image_resize($tmpdestination, $tmpdestination, $action['data']['width'], $action['data']['height'])) {
            $generated[$action['actionid']] = FALSE;
          break;
      
        case 'scale':
          if (!image_scale($tmpdestination, $tmpdestination, $action['data']['width'], $action['data']['height'])) {
            $generated[$action['actionid']] = FALSE;
          }
          break;
        
    }
    file_move($tmpdestination, $destination);
  }
  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)));
    //generation error.
    //@todo watchdog an error.

function _imagecache_get_rulesets() {
  $rulesets = array();
  $result = db_query('SELECT rulesetid, rulesetname FROM {imagecache_rulesets} ORDER BY rulesetname');
  while($row = db_fetch_array($result)) {
    $rulesets[$row['rulesetid']] = $row;
  }
  return $rulesets;
}

function _imagecache_actions_get_by_rulesetid($rulesetid) {
  $result = db_query('SELECT actionid, weight, data FROM {imagecache_actions} where rulesetid=%d order by weight',$rulesetid);
  while($row = db_fetch_array($result)) {
    $row['data'] = unserialize($row['data']);
    $actions[$row['actionid']] = $row;
  }
  return $actions;
}

function imagecache_admin_list() {
  // screw fapi. Its inability to handled nested submit buttons or any sort of additional data on a submit
  // button is frustrating.. So we're gonna do it ourself.

  if (is_array($_POST['ruleset-op'])) {
    foreach($_POST['ruleset-op'] as $rulesetid => $op) {
      $rulesetid = check_plain($rulesetid);
      switch($op) {
        case t('Create Ruleset'):  
          _imagecache_ruleset_create(check_plain($_POST['edit']['newruleset']['name']));
          break;
        case t('Update Ruleset'):  
          _imagecache_ruleset_update($rulesetid, check_plain($_POST['edit']['rulesets'][$rulesetid]['name']));
          break;
        case t('Delete Ruleset'):  
          _imagecache_ruleset_delete($rulesetid);
          break;
        case t('Flush Ruleset'):   
          _imagecache_ruleset_flush($rulesetid);
          break;
        case t('Add Scale'):  
          $action['data'] = array('function' => 'scale');
          $action['rulesetid'] = $rulesetid;
          $action['weight'] = 0; 
          _imagecache_action_create($action);
          break;

        case t('Add Resize'):  
          $action['data'] = array('function' => 'resize');
          $action['rulesetid'] = $rulesetid;
          $action['weight'] = 0; 
          _imagecache_action_create($action);
          break;
        case t('Add Crop'):
          $action['data'] = array('function' => 'crop');
          $action['rulesetid'] = $rulesetid;
          $action['weight'] = 0; 
          _imagecache_action_create($action);

  if (is_array($_POST['action-op'])) {
    foreach($_POST['action-op'] as $actionid => $op) {
      $actionid = check_plain($actionid);
      $action = _imagecache_action_load($actionid);
      switch($op) {
        case t('Update Action'):
          $action['data'] = $_POST['edit']['rulesets'][$action['rulesetid']]['handlers'][$actionid]['data'];
          $action['weight'] = $_POST['edit']['rulesets'][$action['rulesetid']]['handlers'][$actionid]['weight'];
          if (count($action['data'])) {
            foreach($action['data'] as $key => $value) {
              $action['data'][$key] = check_plain($value);
            }
          }
          //debug_msg($action,'action b4 update');
          _imagecache_action_update($action); 
          break;
        case t('Delete Action'):
          _imagecache_action_delete($action);
          break;
      }
    }
  }

  $rulesets = _imagecache_get_rulesets();
  drupal_set_title('Imagecache Administration');
  $form['title'] = array('#type' => 'markup', '#value' => 'Rulesets', '#theme' => 'imagecache_admin_title');
  $form['rulesets']['#tree'] = TRUE;
  if (count($rulesets)) {
    foreach($rulesets as $rulesetid => $ruleset) {
      $form['rulesets'][$rulesetid] = array(
        '#type' => 'fieldset',
        '#title' => t($ruleset['rulesetname']),
        '#collapsible' => TRUE,
      );
      
      $form['rulesets'][$rulesetid]['name'] = array(
        '#type' => 'textfield',
        '#title' => 'NameSpace',
        '#default_value' => $ruleset['rulesetname'],
        '#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/&lt;path to orig&gt;', array('%namespace' => theme('placeholder',$ruleset['rulesetname']))),
      );
      $form['rulesets'][$rulesetid]['handlers'] = array (
        '#type' => 'fieldset',
        '#title' => t('image handlers'),
      );
     
      $form['rulesets'][$rulesetid]['handlers']['#tree'] = FALSE;
      $form['rulesets'][$rulesetid]['handlers'] = _imagecache_actions_form($ruleset['rulesetid']);
      $form['rulesets'][$rulesetid]['ops']['#tree'] = FALSE;
      $form['rulesets'][$rulesetid]['ops']['update'] = array(
        '#type' => 'submit',
        '#name' => 'ruleset-op['.$rulesetid.']',
        '#value' => t('Update Ruleset'),
      );
      $form['rulesets'][$rulesetid]['ops']['delete'] = array(
        '#type' => 'submit',
        '#name' => 'ruleset-op['.$rulesetid.']',
        '#value' => t('Delete Ruleset'),
      );
      $form['rulesets'][$rulesetid]['ops']['flush'] = array(
        '#type' => 'submit',
        '#name' => 'ruleset-op['.$rulesetid.']',
        '#value' => t('Flush Ruleset'),
      );
    }
   

  }
  $form['newruleset'] = array(
    '#type' => 'fieldset',
    '#title' => t('New Ruleset'),
    '#tree' => TRUE,
  );
  $form['newruleset']['name'] = array(
    '#type' => 'textfield',
    '#size' => '64',
    '#title' => t('Name'),
    '#default_value' => '',
    '#description' => t('The name of an imagecache ruleset. It represents a series of actions to be performed when imagecache dynamically generates an image. This will also be used in the url for an image. Please no spaces.'),
  );
  $form['newruleset']['create'] = array(
    '#type' => 'submit',
    '#name' => 'ruleset-op[new]',
    '#value' => 'Create Ruleset',
    '#weight' => 10,
  );
  

  $output .= drupal_get_form('imagecache_admin',$form);
  return $output;
}

/** 
 * load a ruleset by id.
 * @param id
 *    ruleset id.
 */
function _imagecache_ruleset_load($id) {
  $result = db_query('SELECT rulesetid, rulesetname FROM {imagecache_rulesets} WHERE rulesetid = %d', $id);
  return db_fetch_array($result);
/** 
 * load a ruleset by name
 *  @param name
 *    ruleset name
 */

function _imagecache_ruleset_load_by_name($name) {
  $result = db_query('SELECT rulesetid, rulesetname FROM {imagecache_rulesets} WHERE rulesetname = \'%s\'', $name);
  return db_fetch_array($result);
}

/** 
 * create a ruleset 
 * @param name
 *    name of the ruleset to be created.
 */
function _imagecache_ruleset_create($name) {
  db_query('INSERT INTO {imagecache_rulesets} (rulesetname) VALUES (\'%s\')', $name);
  $path = file_directory_path(). '/imagecache/'.$name;
  //debug_msg($path);
}

/**
 * update a ruleset
 * @param id
 *    ruleset id 
 * @param name
 *    new name for the ruleset
 */
function _imagecache_ruleset_update($id, $name) {
  $name = check_plain($name);
  $id = (int)$id;
  db_query('UPDATE {imagecache_rulesets} SET rulesetname =\'%s\' WHERE rulesetid = %d', $name, $id);
  drupal_set_message('Updated ruleset '. $id);
}

function _imagecache_ruleset_delete($id) {
  db_query('DELETE FROM {imagecache_actions} where rulesetid = %d', $id);
  db_query('DELETE FROM {imagecache_rulesets} where rulesetid = %d', $id);
  drupal_set_message('Ruleset '. (int)$id . ' deleted.');

/**
 * flush cached media for a ruleset.
 * @param id
 *   a ruleset id.
 */
function _imagecache_ruleset_flush($id) {
Darrel O'Pry's avatar
Darrel O'Pry committed
  if (user_access('imagecache flush')) {
    drupal_set_message('Flush Ruleset: '. $id);
    $ruleset = _imagecache_ruleset_load($id);
    $rulesetdir = file_directory_path().'/imagecache/'.$ruleset['rulesetname'] .'/'. file_directory_path();
    drupal_set_message('rulesetdir: '. $rulesetdir); 
    $dir = opendir($rulesetdir);
    while ($file = readdir($dir)) {
      if ($file != '.' && $file != '..') {
        unlink($rulesetdir .'/'. $file);
      }
    }
    //unlink(file_directory_path().'/imagecache/'.$ruleset['rulesetname']);
  }
function _imagecache_action_load($id) {
  $result = db_query('SELECT actionid, rulesetid, weight, data FROM {imagecache_actions} WHERE actionid = %d', $id);
  $action = db_fetch_array($result);
  $action['data'] =  unserialize($action['data']);
  return $action;
function _imagecache_action_create($action) {
  //debug_msg($action, 'action@create: ');
  db_query('INSERT INTO {imagecache_actions} (rulesetid, weight, data) VALUES (%d, %d, "%s")', $action['rulesetid'], $action['weight'], serialize($action['data']));
}
function _imagecache_action_update($action) {
  //debug_msg($action, 'action@update');
  db_query('UPDATE {imagecache_actions} SET weight = %d, data = \'%s\' WHERE actionid = %d', $action['weight'], serialize($action['data']), $action['actionid']);
function _imagecache_action_delete($action) {
  _imagecache_ruleset_flush($action['rulesetid']);
  db_query('DELETE FROM {imagecache_actions} WHERE actionid = %d', $action['actionid']);
}
function _imagecache_actions_form($rulesetid) { 
  $form = array(
    '#type' => 'fieldset',
    '#title' => 'actions',
  $actions = _imagecache_actions_get_by_rulesetid($rulesetid);
  if (count($actions)) {
    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' => 'select',
        '#title' => t('Weight'),
        '#options' => array(0,1,2,3,4,5,6,7,8,9,10),
        '#default_value' => 0,
      );
      switch($action['data']['function']) {
        case 'scale':
        case 'resize':
          $form[$actionid]['data']['height'] = array(
            '#type' => 'textfield',
            '#title' => t('Height'),
            '#default_value' => $action['data']['height'],
          );
          $form[$actionid]['data']['width'] = array(
            '#type' => 'textfield',
            '#title' => t('Width'),
            '#default_value' => $action['data']['width'],
          );
          break;
          $form[$actionid]['data']['height'] = array(
            '#type' => 'textfield',
            '#title' => t('Height'),
            '#default_value' => $action['data']['height'],
          );
          $form[$actionid]['data']['width'] = array(
            '#type' => 'textfield',
            '#title' => t('Width'),
            '#default_value' => $action['data']['width'],
          );
          $form[$actionid]['data']['xoffset'] = array(
            '#type' => 'textfield',
            '#title' => t('X Offset'),
            '#default_value' => $action['data']['xoffset'],
          );
          $form[$actionid]['data']['yoffset'] = array(
            '#type' => 'textfield',
            '#title' => t('Y Offset'),
            '#default_value' => $action['data']['yoffset'],
          );
          break;

        case 'watermark':
          //Think about this one...        
      }
      $form[$actionid]['delete'] = array(
        '#type' => 'button',
        '#name' => 'action-op['. $actionid .']',
        '#value' => 'Delete Action',
      );
      $form[$actionid]['update'] = array(
        '#type' => 'button',
        '#name' => 'action-op['. $actionid .']',
        '#value' => 'Update Action',
      );

    }
  }
  $form['newaction'] = array(
    '#type' => 'fieldset',  
    '#title' => t('Add a New Action'),
  );
  $form['newaction']['scale'] = array(
    '#type' => 'submit',
    '#value' => 'Add Scale',
    '#name' => 'ruleset-op['.$rulesetid .']',
  $form['newaction']['resize'] = array(
    '#type' => 'submit',
    '#value' => 'Add Resize',
    '#name' => 'ruleset-op['.$rulesetid .']',
  $form['newaction']['crop'] = array(
    '#type' => 'submit',
    '#value' => 'Add Crop',
    '#name' => 'ruleset-op['.$rulesetid .']',
  );
  return $form;
}


/**
  * Theme an img tag for displaying the image.
  */
function theme_imagecache_display($node, $label, $url, $attributes) {
  return '<img src="'. check_url($url) .'" alt="'. check_plain($node->title) .'" title="'. check_plain($node->title) .'" '. drupal_attributes($attributes) .' />';
}

/**
 * Verify the image module and toolkit settings.
 */
function _imagecache_check_settings() {
  // Sanity check : make sure we've got a working toolkit
  if (!image_get_toolkit()) {
    drupal_set_message(t('Make sure you have a working image toolkit installed and enabled, for more information see: %settings', array('%settings' => l(t('the settings page'), 'admin/settings'))), 'error');
    return false;
  }
  return true;
}

function theme_imagecache_admin_title($element) {
  return '<h2>'.$element['value'].'</h2>';
}