You are here

image.module in Image 5.2

Same filename and directory in other branches
  1. 5 image.module
  2. 6 image.module

File

image.module
View source
<?php

define('IMAGE_ORIGINAL', '_original');
define('IMAGE_PREVIEW', 'preview');
define('IMAGE_THUMBNAIL', 'thumbnail');
define('IMAGE_LINK_HIDDEN', 0);
define('IMAGE_LINK_SHOWN', 1);
define('IMAGE_LINK_NEW', 2);
if (module_exists('views')) {
  include drupal_get_path('module', 'image') . '/views.inc';
  include drupal_get_path('module', 'image') . '/views_defaults.inc';
}

/**
 * Implementation of hook_help
 */
function image_help($section) {
  switch ($section) {
    case 'admin/help#image':
      $output = '<p>' . t('The image module is used to create and administer images for your site. Each image is stored as a post, with thumbnails of the original generated automatically. There are two default thumbnail sizes, thumbnail and preview. The thumbnail size is shown as the preview for image posts and when browsing image galleries. The preview is the default size when first displaying an image node.') . '</p>';
      $output .= '<p>' . t('Image administration allows the image directory and the image sizes to be set.</p><p>
Image galleries are used to organize and display images in galleries.   The list tab allows users to edit existing image gallery names, descriptions, parents and relative position, known as a weight.  The add galleries tab allows you to create a new image gallery defining name, description, parent and weight.') . '</p>';
      $output .= t('<p>You can</p>
<ul>
<li>view image handling messages in <a href="!admin-settings">administer &gt;&gt; settings</a>.</li>
<li>configure image sizes and file directories at <a href="!admin-settings-image">administer &gt;&gt; settings &gt;&gt; image</a>.</li>
<li>use the <a href="!external-http-drupal-org-project-img_assist">image assist module</a> to upload and insert images into posts.</li>
', array(
        '!admin-settings' => url('admin/settings'),
        '!admin-image-galleries' => url('admin/image/galleries'),
        '!admin-settings-image' => url('admin/settings/image'),
        '!external-http-drupal-org-project-img_assist' => 'http://drupal.org/project/img_assist',
      )) . '</ul>';
      $output .= '<p>' . t('For more information please read the configuration and customization handbook <a href="!image">Image page</a>.', array(
        '!image' => 'http://www.drupal.org/handbook/modules/image/',
      )) . '</p>';
      return $output;
  }
}

/**
 * Implementation of hook_node_info
 */
function image_node_info() {
  return array(
    'image' => array(
      'name' => t('Image'),
      'module' => 'image',
      'description' => t('An image (with thumbnail). This is ideal for publishing photographs or screenshots.'),
    ),
  );
}

/**
 * Implementation of hook_perm
 */
function image_perm() {
  return array(
    'create images',
    'view original images',
    'edit own images',
    'edit images',
  );
}

/**
 * Implementation of hook_access
 */
function image_access($op, $node) {
  global $user;
  if ($op == 'create' && user_access('create images')) {
    return TRUE;
  }
  if ($op == 'update' || $op == 'delete') {
    if (user_access('edit images')) {
      return TRUE;
    }
    if (user_access('edit own images') && $user->uid == $node->uid) {
      return TRUE;
    }
  }
}

/**
 * Admin settings callback.
 */
function image_admin_settings() {
  _image_check_settings();
  $form['#submit'] = array(
    'image_settings_sizes_submit' => array(),
  );
  $form['image_updated'] = array(
    '#type' => 'hidden',
    '#value' => variable_get('image_updated', time()),
  );
  $form['paths'] = array(
    '#type' => 'fieldset',
    '#title' => t('File paths'),
  );
  $form['paths']['image_default_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Default image path'),
    '#default_value' => variable_get('image_default_path', 'images'),
    '#description' => t('Subdirectory in the directory %dir where pictures will be stored. Do not include trailing slash.', array(
      '%dir' => file_directory_path(),
    )),
  );
  $form['image_max_upload_size'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum upload size'),
    '#default_value' => variable_get('image_max_upload_size', 800),
    '#field_suffix' => t('KB'),
    '#size' => 12,
    '#description' => t('Maximum file size for image uploads. When a maximum image dimensions is specified for original images the size is checked after resizing. '),
  );
  $form['image_sizes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Image sizes'),
    '#tree' => TRUE,
    '#theme' => 'image_settings_sizes_form',
    '#description' => '<p>' . t("The <em>Scale image</em> operation resizes images so that they fit with in the given dimensions. If only one dimension is specified the other dimension will be computed based on the image's aspect ratio. The <em>Scale and crop image</em> operation resizes images to be exactly the given dimensions. If only one dimension is specified the image will not be cropped, making this is equivalent to <em>Scale image</em>.") . '</p>' . '<p>' . t("Note: 'Original' dimensions will only be used to resize images when they are first uploaded. Existing originals will not be modified.") . '</p>',
    '#validate' => array(
      'image_settings_sizes_validate' => array(),
    ),
  );
  $link_options = array(
    IMAGE_LINK_HIDDEN => t('Hidden'),
    IMAGE_LINK_SHOWN => t('Same window'),
    IMAGE_LINK_NEW => t('New window'),
  );
  $sizes = image_get_sizes();

  // Add some empty rows for user defined sizes.
  $num_sizes = count($sizes);
  for ($i = $num_sizes; $i < $num_sizes + 3; $i++) {
    $sizes['new' . $i] = array(
      'label' => '',
      'operation' => 'scale',
      'width' => '',
      'height' => '',
      'link' => IMAGE_LINK_SHOWN,
      'new' => TRUE,
    );
  }
  foreach ($sizes as $key => $size) {
    $form['image_sizes'][$key]['label'] = array(
      '#type' => 'textfield',
      '#default_value' => $size['label'],
      '#size' => 25,
      '#maxlength' => 32,
    );
    if (_image_is_required_size($key)) {
      $form['image_sizes'][$key]['label']['#attributes'] = array(
        'disabled' => 'disabled',
      );
      $form['image_sizes'][$key]['label']['#required'] = TRUE;
    }
    $form['image_sizes'][$key]['operation'] = array(
      '#type' => 'select',
      '#default_value' => $size['operation'],
      '#options' => array(
        'scale' => t('Scale image'),
        'scale_crop' => t('Scale and crop image'),
      ),
    );
    $form['image_sizes'][$key]['width'] = array(
      '#type' => 'textfield',
      '#default_value' => $size['width'],
      '#size' => 5,
      '#maxlength' => 5,
    );
    $form['image_sizes'][$key]['height'] = array(
      '#type' => 'textfield',
      '#default_value' => $size['height'],
      '#size' => 5,
      '#maxlength' => 5,
    );
    $form['image_sizes'][$key]['link'] = array(
      '#type' => 'select',
      '#default_value' => $size['link'],
      '#options' => $link_options,
    );
  }
  return system_settings_form($form);
}

/**
 * Check that the sizes provided have the required amount of information.
 */
function image_settings_sizes_validate(&$form) {
  foreach (element_children($form) as $key) {

    // If there's a label they must provide at either a height or width.
    if ($key != IMAGE_ORIGINAL && !empty($form[$key]['label']['#value'])) {
      if (empty($form[$key]['width']['#value']) && empty($form[$key]['height']['#value'])) {
        form_set_error("image_sizes][{$key}][width", t('Must specify a width or height.'));
      }
    }
  }
}

/**
 * Make changes to the settings before passing them off to
 * system_settings_form_submit().
 *
 * Remove deleted sizes, and use the label as indexes for new sizes.
 */
function image_settings_sizes_submit($form_id, &$form_values) {
  $old_sizes = image_get_sizes();

  // If the size's operation, or dimensions change we need to rebuild.
  $rebuild = FALSE;
  foreach ($form_values['image_sizes'] as $key => $size) {

    // Changed to the original setting only affect new images and they
    // shouldn't be able to add or remove it.
    if ($key == IMAGE_ORIGINAL) {
      continue;
    }

    // Remove sizes without labels.
    if (empty($size['label'])) {
      unset($form_values['image_sizes'][$key]);
    }

    // Check if only one is set, indicating an addition or removal.
    if (isset($form_values['image_sizes'][$key]) ^ isset($old_sizes[$key])) {
      $rebuild |= TRUE;

      // When adding a new size, we need to assign a key.
      if (isset($form_values['image_sizes'][$key])) {
        unset($form_values['image_sizes'][$key]);
        $new_key = drupal_strtolower(drupal_substr($size['label'], 0, 32));
        $form_values['image_sizes'][$new_key] = $size;
      }
    }
    else {
      if (isset($form_values['image_sizes'][$key]) && isset($old_sizes[$key])) {

        // Did the operation, height or width change?
        foreach (array(
          'operation',
          'height',
          'width',
        ) as $field) {
          $rebuild |= $form_values['image_sizes'][$key][$field] != $old_sizes[$key][$field];
        }
      }
    }
  }

  // If we've changed anything update the image_update variable so the
  // derivative images are rebuilt.
  if ($rebuild) {
    drupal_set_message(t('Changes to the images sizes mean that the derivative images will need to be regenerated.'));
    $form_values['image_updated'] = time();
  }
  return system_settings_form_submit($form_id, $form_values);
}
function theme_image_settings_sizes_form(&$form) {
  $header = array(
    t('Label'),
    t('Operation'),
    t('Width'),
    t('Height'),
    t('Link'),
  );
  foreach (element_children($form) as $key) {
    $row = array();
    $row[] = drupal_render($form[$key]['label']);
    $row[] = drupal_render($form[$key]['operation']);
    $row[] = drupal_render($form[$key]['width']);
    $row[] = drupal_render($form[$key]['height']);
    $row[] = drupal_render($form[$key]['link']);
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Implementation of hook_menu
 */
function image_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'node/add/image',
      'title' => t('Image'),
      'access' => user_access('create images'),
    );
    $items[] = array(
      'path' => 'image/view',
      'title' => t('image'),
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
      'callback' => 'image_fetch',
    );
    $items[] = array(
      'path' => 'admin/settings/image',
      'title' => t('Images'),
      'description' => t('Configure the location of image files and image sizes. Also, if enabled, configure image attachments and options for image galleries.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'image_admin_settings',
      ),
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/image/nodes',
      'title' => t('Files and sizes'),
      'description' => t('Configure the location of image files and image sizes.'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => '-10',
    );
  }
  return $items;
}

/**
 * Implements hook_cron. (deletes old temp images)
 */
function image_cron() {
  $path = file_directory_path() . '/' . variable_get('image_default_path', 'images') . '/temp';
  $files = file_scan_directory(file_create_path($path), '.*');
  foreach ($files as $file => $info) {
    if (time() - filemtime($file) > 60 * 60 * 6) {
      file_delete($file);
    }
  }
}

/**
 * Implementation of hook_node_operations().
 */
function image_node_operations() {
  $operations = array(
    'rebuild_thumbs' => array(
      'label' => t('Rebuild derivative images'),
      'callback' => 'image_operations_rebuild',
    ),
  );
  return $operations;
}
function image_operations_rebuild($nids) {
  foreach ($nids as $nid) {
    if ($node = node_load($nid)) {
      if ($node->type == 'image') {
        $node->rebuild_images = TRUE;
        image_update($node);
      }
    }
  }
}

/**
 * Implementation of hook_prepare().
 */
function image_prepare(&$node, $field_name = NULL) {

  // We need to be aware that a user may try to edit multiple image nodes at
  // once. By using the $nid variable each node's files can be stored separately
  // in the session.
  $nid = $node->nid ? $node->nid : 'new_node';

  // When you enter the edit view the first time we need to clear our files in
  // session for this node. This is so if you upload a file, then decide you
  // don't want it and reload the form (without posting), the files will be
  // discarded.
  if (count($_POST) == 0) {
    unset($_SESSION['image_new_files'][$nid]);
  }
  if (is_null($field_name)) {
    $field_name = 'image';
  }
  if ($file = file_check_upload($field_name)) {

    // Ensure the file is an image.
    $image_info = image_get_info($file->filepath);
    if (!$image_info || empty($image_info['extension'])) {
      form_set_error($field_name, t('Uploaded file is not a valid image. Only JPG, PNG and GIF files are allowed.'));
      return;
    }

    // Save the file to the temp directory.
    $file = file_save_upload($field_name, _image_filename($file->filename, IMAGE_ORIGINAL, TRUE));
    if (!$file) {
      return;
    }

    // Resize the original.
    $aspect_ratio = $image_info['height'] / $image_info['width'];
    $original_size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
    if (!empty($original_size['width']) && !empty($original_size['height'])) {
      $result = image_scale($file->filepath, $file->filepath, $original_size['width'], $original_size['height']);
      if ($result) {
        clearstatcache();
        $file->filesize = filesize($file->filepath);
        drupal_set_message(t('The original image was resized to fit within the maximum allowed resolution of %width x %height pixels.', array(
          '%width' => $original_size['width'],
          '%height' => $original_size['height'],
        )));
      }
    }

    // Check the file size limit.
    if ($file->filesize > variable_get('image_max_upload_size', 800) * 1024) {
      form_set_error($field_name, t('The image you uploaded was too big. You are only allowed upload files less than %max_size but your file was %file_size.', array(
        '%max_size' => format_size(variable_get('image_max_upload_size', 800) * 1024),
        '%file_size' => format_size($file->filesize),
      )));
      file_delete($file->filepath);
      return;
    }

    // We're good to go.
    $node->images[IMAGE_ORIGINAL] = $file->filepath;
    $node->rebuild_images = FALSE;
    $node->new_file = TRUE;

    // Call hook to allow other modules to modify the original image.
    module_invoke_all('image_alter', $node, $file->filepath, IMAGE_ORIGINAL);
    _image_build_derivatives($node, TRUE);

    // Store the new file into the session.
    $_SESSION['image_new_files'][$nid] = $node->images;
  }
  else {
    if (isset($_SESSION['image_new_files'][$nid])) {
      $node->images = $_SESSION['image_new_files'][$nid];
    }
  }
}

/**
 * Implementation of hook_file_download().
 *
 * Note that in Drupal 5, the upload.module's hook_file_download() checks its
 * permissions for all files in the {files} table. We store our file
 * information in {files} if private files transfers are selected and the
 * upload.module is enabled, users will the 'view uploaded files'permission to
 * view images.
 */
function image_file_download($filename) {
  $filepath = file_create_path($filename);
  $result = db_query("SELECT i.nid, f.filemime, f.filesize FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE f.filepath = '%s'", $filepath);
  if ($file = db_fetch_object($result)) {
    $node = node_load(array(
      'type' => 'image',
      'nid' => $file->nid,
    ));
    if ($node) {
      if (node_access('view', $node)) {

        // The user either needs to have 'view original images' permission or
        // the path must be listed for something other than the node's original
        // size. This will be the case when the orignal is smaller than a
        // derivative size.
        $images = (array) $node->images;
        unset($images[IMAGE_ORIGINAL]);
        if (user_access('view original images') || in_array($filepath, $images)) {
          return array(
            'Content-Type: ' . mime_header_encode($file->filemime),
            'Content-Length: ' . (int) $file->filesize,
          );
        }
      }
      return -1;
    }
  }
}

/**
 * Implementation of hook_link.
 */
function image_link($type, $node, $main = 0) {
  $links = array();
  if ($type == 'node' && $node->type == 'image' && !$main) {
    $request = $_GET['size'] ? $_GET['size'] : IMAGE_PREVIEW;
    foreach (image_get_sizes() as $key => $size) {
      if ($size['link']) {

        // For smaller images some derivative images may not have been created.
        // The thumbnail and preview images will be equal to the original images
        // but other sizes will not be set.
        if (isset($node->images[$key]) && $node->images[$key] != $node->images[$request]) {
          if ($size['link'] == IMAGE_LINK_NEW) {
            $links['image_size_' . $key] = array(
              'title' => $size['label'],
              'href' => "image/view/{$node->nid}/{$key}",
              'attributes' => array(
                'target' => '_blank',
              ),
            );
          }
          else {
            $links['image_size_' . $key] = array(
              'title' => $size['label'],
              'href' => 'node/' . $node->nid,
              'query' => 'size=' . urlencode($key),
            );
          }
        }
      }
    }
    if (!user_access('view original images')) {
      unset($links['image_size_' . IMAGE_ORIGINAL]);
    }
  }
  return $links;
}

/**
 * Implementation of hook_block.
 *
 * Offers 2 blocks: latest image and random image. Skip if Views module is 
 * enabled.
 */
function image_block($op = 'list', $delta = 0, $edit = array()) {
  if (module_exists('views')) {
    return;
  }
  switch ($op) {
    case 'list':
      $block[0]['info'] = t('Latest image');
      $block[1]['info'] = t('Random image');
      return $block;
    case 'configure':
      $form['number_images'] = array(
        '#type' => 'select',
        '#title' => t('Number of images to display'),
        '#options' => drupal_map_assoc(range(1, 99)),
        '#default_value' => variable_get("image_block_{$delta}_number_images", 1),
      );
      return $form;
    case 'view':
      if (user_access('access content')) {
        switch ($delta) {
          case 0:
            $images = image_get_latest(variable_get("image_block_{$delta}_number_images", 1));
            $block['subject'] = t('Latest image');
            $block['content'] = theme('image_block_latest', $images, IMAGE_THUMBNAIL);
            break;
          case 1:
            $images = image_get_random(variable_get("image_block_{$delta}_number_images", 1));
            $block['subject'] = t('Random image');
            $block['content'] = theme('image_block_random', $images, IMAGE_THUMBNAIL);
            break;
        }
      }
      return $block;
    case 'save':
      variable_set("image_block_{$delta}_number_images", $edit['number_images']);
      break;
  }
}
function image_form_add_thumbnail($form, $form_values) {
  if ($form_values['images']['thumbnail']) {
    $node = (object) $form_values;
    $form['#title'] = t('Thumbnail');
    $form['#value'] = image_display($node, IMAGE_THUMBNAIL);
  }
  return $form;
}

/**
 * Implementation of hook_form
 */
function image_form(&$node, &$param) {
  _image_check_settings();
  $type = node_get_types('type', $node);
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => check_plain($type->title_label),
    '#size' => 60,
    '#maxlength' => 128,
    '#required' => TRUE,
    '#default_value' => $node->title,
  );
  $form['images']['#tree'] = TRUE;
  foreach (image_get_sizes() as $key => $size) {
    $form['images'][$key] = array(
      '#type' => 'value',
      '#value' => $node->images[$key],
    );
  }
  $form['thumbnail'] = array(
    '#type' => 'item',
    '#weight' => -10,
    '#after_build' => array(
      'image_form_add_thumbnail',
    ),
  );
  $form['rebuild_images'] = array(
    '#type' => 'checkbox',
    '#title' => t('Rebuild derivative images'),
    '#default_value' => FALSE,
    '#description' => t('Check this to rebuild the derivative images for this node.'),
  );
  $form['#attributes'] = array(
    "enctype" => "multipart/form-data",
  );
  $form['image'] = array(
    '#type' => 'file',
    '#title' => t('Image'),
    '#size' => 40,
    '#description' => t('Click "Browse..." to select an image to upload.'),
    '#weight' => -3,
  );
  if ($type->has_body) {
    $form['body_filter']['body'] = array(
      '#type' => 'textarea',
      '#title' => check_plain($type->body_label),
      '#required' => $type->min_word_count > 0,
      '#default_value' => $node->body,
    );
    $form['body_filter']['format'] = filter_form($node->format);
  }
  return $form;
}
function image_validate($node) {
  $nid = $node->nid ? $node->nid : 'new_node';
  if (!isset($node->images[IMAGE_ORIGINAL]) && !isset($_SESSION['image_new_files'][$nid])) {
    form_set_error('image', t('You must upload an image.'));
  }
}
function image_submit($node) {
  $nid = $node->nid ? $node->nid : 'new_node';
  if (isset($_SESSION['image_new_files'][$nid])) {
    $node->new_file = TRUE;
    $node->rebuild_images = FALSE;
    $node->images = $_SESSION['image_new_files'][$nid];
    unset($_SESSION['image_new_files'][$nid]);
  }
}

/**
 * Implementation of hook_view
 */
function image_view($node, $teaser = 0, $page = 0) {
  $sizes = image_get_sizes();
  $size = IMAGE_PREVIEW;
  if (isset($_GET['size'])) {

    // Invalid size specified.
    if (!isset($sizes[$_GET['size']])) {
      drupal_goto("node/{$node->nid}");
    }
    $size = $_GET['size'];

    // Not allowed to view the original.
    if ($size == IMAGE_ORIGINAL && !user_access('view original images')) {
      drupal_goto("node/{$node->nid}");
    }
  }
  $node = node_prepare($node, $teaser);
  $node->content['image'] = array(
    '#value' => theme($teaser ? 'image_teaser' : 'image_body', $node, $size),
    '#weight' => 0,
  );
  return $node;
}

/**
 * Implementation of hook_load
 */
function image_load(&$node) {
  $result = db_query("SELECT i.image_size, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
  $node->images = array();
  while ($file = db_fetch_object($result)) {
    $node->images[$file->image_size] = file_create_path($file->filepath);
  }
  $original_path = $node->images[IMAGE_ORIGINAL];
  if (empty($original_path)) {

    // There's no original image, we're in trouble...
    return;
  }
  $node->rebuild_images = FALSE;

  // Figure out which sizes should have been generated.
  $all_sizes = image_get_sizes();
  unset($all_sizes[IMAGE_ORIGINAL]);
  $needed_sizes = array_keys(image_get_derivative_sizes($original_path));
  $unneeded_sizes = array_diff(array_keys($all_sizes), $needed_sizes);

  // Derivative sizes that are larger than the original get set to the
  // original.
  foreach ($unneeded_sizes as $key) {
    if (empty($node->images[$key])) {
      $node->images[$key] = $original_path;
    }
    else {

      // Need to remove an extra derivative image in the database.
      $node->rebuild_images = TRUE;
    }
  }

  // Check that the derivative images are present and current.
  foreach ($needed_sizes as $key) {

    // If the file is missing or created after the last change to the sizes,
    // rebuild the derivatives.
    if (empty($node->images[$key]) || !file_exists($node->images[$key])) {
      $node->rebuild_images = TRUE;
    }
    elseif (filemtime($node->images[$key]) < variable_get('image_updated', 0)) {
      $node->rebuild_images = TRUE;
    }
  }

  // Correct any problems with the derivative images.
  if ($node->rebuild_images) {
    image_update($node);
    watchdog('image', t('Derivative images were regenerated for %title.', array(
      '%title' => $node->title,
    )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid));
  }
}

/**
 * Implementation of hook_insert
 */
function image_insert($node) {

  // Derivative images that aren't needed are set to the original file. Make
  // note of the current path before calling _image_insert() because if it's
  // in the temp directory it'll be moved. We'll need it later to determine
  // which derivative images need to be saved with _image_insert().
  $original_path = $node->images[IMAGE_ORIGINAL];

  // Save the original first so that it if it's moved the derivatives are
  // placed in the correct directory.
  _image_insert($node, IMAGE_ORIGINAL, $original_path);
  $sizes = image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]);
  foreach ($sizes as $key => $size_info) {
    if (!empty($node->images[$key]) && $node->images[$key] != $original_path) {
      _image_insert($node, $key, $node->images[$key]);
    }
  }
}

/**
 * Implementation of hook_update
 *
 * Take $node by reference so we can use this to save the node after
 * rebuilding derivatives.
 */
function image_update(&$node) {
  if ($node->new_file || $node->rebuild_images) {

    // Derivative images that aren't needed are set to the original file. Make
    // note of the current path before calling _image_insert() because if it's
    // in the temp directory it'll be moved. We'll need it later to determine
    // which derivative images need to be saved with _image_insert().
    $original_path = $node->images[IMAGE_ORIGINAL];
    if ($node->new_file) {

      // The derivative images were built during image_prepare() or
      // image_create_node_from() so all we need to do is remove all the old,
      // existing images.
      // Remove all the existing images.
      $result = db_query("SELECT f.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
      while ($file = db_fetch_object($result)) {
        file_delete(file_create_path($file->filepath));
        db_query("DELETE FROM {files} WHERE fid = %d", $file->fid);
      }
      db_query("DELETE FROM {image} WHERE nid = %d", $node->nid);

      // Save the original first so that it if it's moved the derivatives are
      // placed in the correct directory.
      _image_insert($node, IMAGE_ORIGINAL, $original_path);
    }
    else {
      if ($node->rebuild_images) {

        // Find the original image.
        $original_file = db_fetch_object(db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND i.image_size = '%s'", $node->nid, IMAGE_ORIGINAL));

        // Delete all but the original image.
        $result = db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND f.fid <> %d", $node->nid, $original_file->fid);
        while ($file = db_fetch_object($result)) {

          // Beware of derivative images that have the same path as the original.
          if ($file->filepath != $original_file->filepath) {
            file_delete(file_create_path($file->filepath));
          }
          db_query("DELETE FROM {files} WHERE fid = %d", $file->fid);
          db_query("DELETE FROM {image} WHERE fid = %d", $file->fid);
        }
        _image_build_derivatives($node, FALSE);

        // Prevent multiple rebuilds.
        $node->rebuild_images = FALSE;
      }
    }
    $sizes = image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]);
    foreach ($sizes as $key => $size_info) {
      if (!empty($node->images[$key]) && $node->images[$key] != $original_path) {
        _image_insert($node, $key, $node->images[$key]);
      }
    }
  }
}

/**
 * Implementation of hook_delete.
 */
function image_delete($node) {
  $result = db_query('SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d', $node->nid);
  while ($file = db_fetch_object($result)) {
    file_delete(file_create_path($file->filepath));
    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
  }
  db_query("DELETE FROM {image} WHERE nid = %d", $node->nid);
}

/**
 * Create an <img> tag for an image.
 */
function image_display(&$node, $label = IMAGE_PREVIEW, $attributes = array()) {
  if (empty($node->images[$label])) {
    return;
  }
  $image_info = image_get_info(file_create_path($node->images[$label]));
  $attributes['class'] = "image image-{$label} " . (isset($attributes['class']) ? $attributes['class'] : "");

  // Only output width/height attributes if image_get_info() was able to detect
  // the image dimensions, since certain browsers interpret an empty attribute
  // value as zero.
  if (!empty($image_info['width'])) {
    $attributes['width'] = $image_info['width'];
  }
  if (!empty($image_info['height'])) {
    $attributes['height'] = $image_info['height'];
  }
  return theme('image_display', $node, $label, file_create_url($node->images[$label]), $attributes);
}

/**
 * Fetches an image file, allows "shorthand" image urls such of the form:
 * image/view/$nid/$label
 * (e.g. image/view/25/thumbnail or image/view/14)
 */
function image_fetch($nid = 0, $size = IMAGE_PREVIEW) {
  if ($size == IMAGE_ORIGINAL && !user_access('view original images')) {
    return drupal_access_denied();
  }
  if (isset($nid)) {
    $node = node_load(array(
      'type' => 'image',
      'nid' => $nid,
    ));
    if ($node) {
      if (!node_access('view', $node)) {
        return drupal_access_denied();
      }
      if (isset($node->images[$size])) {
        $file = $node->images[$size];
        $headers = image_file_download($file);
        if ($headers == -1) {
          return drupal_access_denied();
        }
        file_transfer($file, $headers);
      }
    }
  }
  return drupal_not_found();
}

/**
 * Theme a teaser
 */
function theme_image_teaser($node) {
  return l(image_display($node, IMAGE_THUMBNAIL), 'node/' . $node->nid, array(), NULL, NULL, TRUE, TRUE);
}

/**
 * Theme a body
 */
function theme_image_body($node, $size) {
  return image_display($node, $size);
}

/**
 * Theme an img tag for displaying the image.
 */
function theme_image_display($node, $label, $url, $attributes) {
  return theme('image', $url, $node->title, $node->title, $attributes, FALSE);
}

/**
 * Theme a random block
 */
function theme_image_block_random($images, $size) {
  $output = '';
  foreach ($images as $image) {
    $output .= l(image_display($image, $size), 'node/' . $image->nid, array(), NULL, NULL, FALSE, TRUE);
  }
  return $output;
}

/**
 * Theme a latest block
 */
function theme_image_block_latest($images, $size) {
  $output = '';
  foreach ($images as $image) {
    $output .= l(image_display($image, $size), 'node/' . $image->nid, array(), NULL, NULL, FALSE, TRUE);
  }
  return $output;
}

/**
 * Fetch a random N image(s) - optionally from a given term.
 */
function image_get_random($count = 1, $tid = 0) {
  if ($tid != 0) {
    $result = db_query_range(db_rewrite_sql("SELECT DISTINCT(n.nid), RAND() AS rand FROM {term_node} tn LEFT JOIN {node} n ON n.nid = tn.nid WHERE n.type='image' AND n.status = 1 AND tn.tid = %d ORDER BY rand"), $tid, 0, $count);
  }
  else {
    $result = db_query_range(db_rewrite_sql("SELECT DISTINCT(n.nid), RAND() AS rand FROM {node} n WHERE n.type = 'image' AND n.status = 1 ORDER BY rand"), 0, $count);
  }
  $output = array();
  while ($nid = db_fetch_object($result)) {
    $output[] = node_load(array(
      'nid' => $nid->nid,
    ));
  }
  return $output;
}

/**
 * Fetch the latest N image(s) - optionally from a given term.
 */
function image_get_latest($count = 1, $tid = 0) {
  if ($tid != 0) {
    $result = db_query_range(db_rewrite_sql("SELECT n.nid FROM {term_node} tn LEFT JOIN {node} n ON n.nid=tn.nid WHERE n.type='image' AND n.status=1 AND tn.tid=%d ORDER BY n.changed DESC"), $tid, 0, $count);
  }
  else {
    $result = db_query_range(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.type = 'image' AND n.status = 1 ORDER BY n.changed DESC"), 0, $count);
  }
  $output = array();
  while ($nid = db_fetch_object($result)) {
    $output[] = node_load(array(
      'nid' => $nid->nid,
    ));
  }
  return $output;
}

/**
 * Verify the image module and toolkit settings.
 */
function _image_check_settings() {

  // File paths
  $image_path = file_create_path(variable_get('image_default_path', 'images'));
  $temp_path = rtrim($image_path, '/') . '/temp';
  if (!file_check_directory($image_path, FILE_CREATE_DIRECTORY, 'image_default_path')) {
    return false;
  }
  if (!file_check_directory($temp_path, FILE_CREATE_DIRECTORY, 'image_default_path')) {
    return false;
  }

  // Sanity check : make sure we've got a working toolkit
  if (!image_get_toolkit()) {
    drupal_set_message(t('No image toolkit is currently enabled. Without one the image module will not be able to resize your images. You can select one from the <a href="!link">image toolkit settings page</a>.', array(
      '!link' => url('admin/settings/image-toolkit'),
    )), 'error');
    return false;
  }
  return true;
}

/**
 * Determine which sizes of derivative images need to be built for this image.
 *
 * @param $image_path
 *   String file path to the image.
 * @return
 *   Returns a subset of image_get_sizes()'s results depending on what
 *   derivative images are needed.
 */
function image_get_derivative_sizes($image_path) {
  $sizes = array();

  // Can't do much if we can't read the image.
  if (!($image_info = image_get_info($image_path))) {
    return $sizes;
  }
  $all_sizes = image_get_sizes(NULL, $image_info['height'] / $image_info['width']);
  foreach ($all_sizes as $key => $size) {

    // We don't want to include the original.
    if ($key == IMAGE_ORIGINAL) {
      continue;
    }

    // If the original isn't bigger than the requested size then there's no
    // need to resize it.
    if ($image_info['width'] > $size['width'] || $image_info['height'] > $size['height']) {
      $sizes[$key] = $size;
    }
  }
  return $sizes;
}

/**
 * Generate image derivatives.
 */
function _image_build_derivatives(&$node, $temp = FALSE) {

  // sanity check:
  if (!_image_check_settings()) {
    return FALSE;
  }
  $original_path = file_create_path($node->images[IMAGE_ORIGINAL]);

  // Figure out which sizes we need to generate.
  $all_sizes = image_get_sizes();
  $needed_sizes = image_get_derivative_sizes($original_path);
  $unneeded_sizes = array_diff(array_keys($all_sizes), array_keys($needed_sizes));

  // Images that don't need a derivative image get set to the original.
  foreach ($unneeded_sizes as $key) {
    $node->images[$key] = $original_path;
  }

  // Resize for the necessary sizes.
  $image_info = image_get_info($original_path);
  foreach ($needed_sizes as $key => $size) {
    $destination = _image_filename($original_path, $key, $temp);
    $status = FALSE;
    switch ($size['operation']) {

      // Depending on the operation, the image will be scaled or resized & cropped
      case 'scale':
        $status = image_scale($original_path, $destination, $size['width'], $size['height']);
        break;
      case 'scale_crop':

        // This is based on Drupal 6's image_scale_and_crop(). Scales the
        // image so that the image's smaller dimension matches the
        // requested size and the crops any overlap on the image's larger
        // dimension.
        $scale = max($size['width'] / $image_info['width'], $size['height'] / $image_info['height']);
        $x = round(($image_info['width'] * $scale - $size['width']) / 2);
        $y = round(($image_info['height'] * $scale - $size['height']) / 2);
        if (image_resize($original_path, $destination, $image_info['width'] * $scale, $image_info['height'] * $scale)) {
          $status = image_crop($destination, $destination, $x, $y, $size['width'], $size['height']);
        }
        break;
    }
    if (!$status) {
      drupal_set_message(t('Unable to create scaled %label image', array(
        '%label' => $size['label'],
      )), 'error');
      return FALSE;
    }

    // Set standard file permissions for webserver-generated files
    @chmod($destination, 0664);
    $node->images[$key] = $destination;
    module_invoke_all('image_alter', $node, $destination, $key);
  }
}

/**
 * Creates an image filename.
 */
function _image_filename($filename, $label = IMAGE_ORIGINAL, $temp = FALSE) {
  $path = file_directory_path() . '/' . variable_get('image_default_path', 'images');
  if ($temp) {
    $path .= '/temp';
  }
  $filename = basename($filename);

  // Insert the resized name in non-original images
  if ($label && $label != IMAGE_ORIGINAL) {
    $pos = strrpos($filename, '.');
    if ($pos === false) {

      // The file had no extension - which happens in really old image.module
      // versions, so figure out the extension.
      $image_info = image_get_info(file_create_path($path . '/' . $filename));
      $filename = $filename . '.' . $label . '.' . $image_info['extension'];
    }
    else {
      $filename = substr($filename, 0, $pos) . '.' . $label . substr($filename, $pos);
    }
  }
  return file_create_path($path . '/' . $filename);
}

/**
 * Helper function to return the defined sizes (or proper defaults).
 *
 * @param $size
 *   An optional string to return only the image size with the specified key.
 * @param $aspect_ratio
 *   Float value with the ratio of image height / width. If a size has only one
 *   dimension provided this will be used to compute the other.
 * @return
 *   An associative array with width, height, and label fields for the size.
 *   If a $size parameter was specified and it cannot be found FALSE will be 
 *   returned.
 */
function image_get_sizes($size = NULL, $aspect_ratio = NULL) {
  $defaults = array(
    IMAGE_ORIGINAL => array(
      'width' => '',
      'height' => '',
      'label' => t('Original'),
      'operation' => 'scale',
      'link' => IMAGE_LINK_SHOWN,
    ),
    IMAGE_THUMBNAIL => array(
      'width' => 100,
      'height' => 100,
      'label' => t('Thumbnail'),
      'operation' => 'scale',
      'link' => IMAGE_LINK_SHOWN,
    ),
    IMAGE_PREVIEW => array(
      'width' => 640,
      'height' => 640,
      'label' => t('Preview'),
      'operation' => 'scale',
      'link' => IMAGE_LINK_SHOWN,
    ),
  );
  $sizes = array();
  foreach (variable_get('image_sizes', $defaults) as $key => $val) {

    // Only return sizes with a label.
    if (!empty($val['label'])) {

      // For a size with only one dimension specified, compute the other
      // dimension based on an aspect ratio.
      if ($aspect_ratio && (empty($val['width']) || empty($val['height']))) {
        if (empty($val['height']) && !empty($val['width'])) {
          $val['height'] = (int) round($val['width'] * $aspect_ratio);
        }
        elseif (empty($val['width']) && !empty($val['height'])) {
          $val['width'] = (int) round($val['height'] / $aspect_ratio);
        }
      }
      $sizes[$key] = $val;
    }
  }

  // If they requested a specific size return only that.
  if (isset($size)) {

    // Only return an array if it's available.
    return isset($sizes[$size]) ? $sizes[$size] : FALSE;
  }
  return $sizes;
}

/**
 * Helper function to preserve backwards compatibility. This has been
 * deprecated in favor of image_get_sizes().
 *
 * @TODO: Remove this in a future version.
 */
function _image_get_sizes($size = NULL, $aspect_ratio = NULL) {
  return image_get_sizes($size, $aspect_ratio);
}

/**
 * Is a given size a built-in, required size?
 * @param $size
 *   One of the keys in the array returned by image_get_sizes().
 * @return boolean
 */
function _image_is_required_size($size) {
  return in_array($size, array(
    IMAGE_THUMBNAIL,
    IMAGE_PREVIEW,
    IMAGE_ORIGINAL,
  ));
}

/**
 * Moves temporary (working) images to the final directory and stores
 * relevant information in the files table
 */
function _image_insert(&$node, $size, $image_path) {
  $original_path = $node->images[IMAGE_ORIGINAL];
  if (file_move($image_path, _image_filename($original_path, $size))) {

    // Update the node to reflect the actual filename, it may have been changed
    // if a file of the same name already existed.
    $node->images[$size] = $image_path;
    $image_info = image_get_info($image_path);
    $fid = db_next_id('{files}_fid');
    db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', '%s')", $fid, $node->nid, $size, $image_path, $image_info['mime_type'], $image_info['file_size']);
    db_query("INSERT INTO {image} (nid, image_size, fid) VALUES (%d, '%s', %d)", $node->nid, $size, $fid);
  }
}

/**
 * Function to other modules to use to create image nodes.
 *
 * @param $filepath
 *   String filepath of an image file. Note that this file will be moved into 
 *   the image module's images directory.
 * @param $title
 *   String to be used as the node's title. If this is ommitted the filename 
 *   will be used.
 * @param $body 
 *   String to be used as the node's body.
 * @param $taxonomy
 *   Taxonomy terms to assign to the node if the taxonomy.module is installed.
 * @return 
 *   A node object if the node is created successfully or FALSE on error.
 */
function image_create_node_from($filepath, $title = NULL, $body = '', $taxonomy = NULL) {
  global $user;
  if (!user_access('create images')) {
    drupal_access_denied();
  }

  // Ensure it's a valid image.
  if (!($image_info = image_get_info($filepath))) {
    return FALSE;
  }

  // Make sure we can copy the file into our temp directory.
  $original_path = $filepath;
  if (!file_copy($filepath, _image_filename($filepath, IMAGE_ORIGINAL, TRUE))) {
    return FALSE;
  }

  // Resize the original image.
  $aspect_ratio = $image_info['height'] / $image_info['width'];
  $size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
  if (!empty($size['width']) && !empty($size['height'])) {
    image_scale($filepath, $filepath, $size['width'], $size['height']);
  }

  // Build the node.
  $node = new stdClass();
  $node->type = 'image';
  $node->uid = $user->uid;
  $node->name = $user->name;
  $node->title = isset($title) ? $title : basename($filepath);
  $node->body = $body;

  // Set the node's defaults... (copied this from node and comment.module)
  $node_options = variable_get('node_options_' . $node->type, array(
    'status',
    'promote',
  ));
  $node->status = in_array('status', $node_options);
  $node->promote = in_array('promote', $node_options);
  if (module_exists('comment')) {
    $node->comment = variable_get("comment_{$node->type}", COMMENT_NODE_READ_WRITE);
  }
  if (module_exists('taxonomy')) {
    $node->taxonomy = $taxonomy;
  }
  $node->new_file = TRUE;
  $node->images[IMAGE_ORIGINAL] = $filepath;

  // Save the node.
  $node = node_submit($node);
  node_save($node);

  // Remove the original image now that the import has completed.
  file_delete($original_path);
  return $node;
}

/**
 * Implementation of hook_panels_include_directory().
 */
function image_panels_include_directory($plugintype) {
  switch ($plugintype) {
    case 'content_types':
      return $plugintype;
  }
}

Functions

Namesort descending Description
image_access Implementation of hook_access
image_admin_settings Admin settings callback.
image_block Implementation of hook_block.
image_create_node_from Function to other modules to use to create image nodes.
image_cron Implements hook_cron. (deletes old temp images)
image_delete Implementation of hook_delete.
image_display Create an <img> tag for an image.
image_fetch Fetches an image file, allows "shorthand" image urls such of the form: image/view/$nid/$label (e.g. image/view/25/thumbnail or image/view/14)
image_file_download Implementation of hook_file_download().
image_form Implementation of hook_form
image_form_add_thumbnail
image_get_derivative_sizes Determine which sizes of derivative images need to be built for this image.
image_get_latest Fetch the latest N image(s) - optionally from a given term.
image_get_random Fetch a random N image(s) - optionally from a given term.
image_get_sizes Helper function to return the defined sizes (or proper defaults).
image_help Implementation of hook_help
image_insert Implementation of hook_insert
image_link Implementation of hook_link.
image_load Implementation of hook_load
image_menu Implementation of hook_menu
image_node_info Implementation of hook_node_info
image_node_operations Implementation of hook_node_operations().
image_operations_rebuild
image_panels_include_directory Implementation of hook_panels_include_directory().
image_perm Implementation of hook_perm
image_prepare Implementation of hook_prepare().
image_settings_sizes_submit Make changes to the settings before passing them off to system_settings_form_submit().
image_settings_sizes_validate Check that the sizes provided have the required amount of information.
image_submit
image_update Implementation of hook_update
image_validate
image_view Implementation of hook_view
theme_image_block_latest Theme a latest block
theme_image_block_random Theme a random block
theme_image_body Theme a body
theme_image_display Theme an img tag for displaying the image.
theme_image_settings_sizes_form
theme_image_teaser Theme a teaser
_image_build_derivatives Generate image derivatives.
_image_check_settings Verify the image module and toolkit settings.
_image_filename Creates an image filename.
_image_get_sizes Helper function to preserve backwards compatibility. This has been deprecated in favor of image_get_sizes().
_image_insert Moves temporary (working) images to the final directory and stores relevant information in the files table
_image_is_required_size Is a given size a built-in, required size?

Constants