You are here

image_captcha.module in CAPTCHA 5.3

Implementation of image CAPTCHA for use with the CAPTCHA module

Loosely Based on MyCaptcha by Heine Deelstra (http://heine.familiedeelstra.com/mycaptcha-download)

File

image_captcha/image_captcha.module
View source
<?php

/**
 * @file Implementation of image CAPTCHA for use with the CAPTCHA module
 *
 * Loosely Based on MyCaptcha by Heine Deelstra
 * (http://heine.familiedeelstra.com/mycaptcha-download)
 *
 */
define('IMAGE_CAPTCHA_ALLOWED_CHARACTERS', 'aAbBCdEeFfGHhijKLMmNPQRrSTtWXYZ23456789%$#!@+?*');

/**
 * Implementation of hook_help().
 */
function image_captcha_help($section) {
  switch ($section) {
    case 'admin/user/captcha/image_captcha':
      $output = '<p>' . t('The image CAPTCHA is a popular challenge where a random textual code is obfuscated in an image. The image is generated on the fly for each request, which is rather CPU intensive for the server. Be careful with the size and computation related settings.') . '</p>';
      if (in_array('Image', image_captcha_captcha('list'))) {
        $result = image_captcha_captcha('generate', 'Image');
        $img = $result['form']['captcha_image']['#value'];
        $output .= t('<p>Example image, generated with the current settings:</p>!img', array(
          '!img' => $img,
        ));
      }
      return $output;
  }
}

/**
 * Implementation of hook_menu().
 */
function image_captcha_menu($may_cache) {
  $items = array();
  if ($may_cache) {

    // add an administration tab for image_captcha
    $items[] = array(
      'path' => 'admin/user/captcha/image_captcha',
      'title' => t('Image CAPTCHA'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'image_captcha_settings_form',
      ),
      'type' => MENU_LOCAL_TASK,
    );

    // callback for generating an image
    $items[] = array(
      'path' => 'image_captcha',
      'type' => MENU_CALLBACK,
      'access' => TRUE,
      'callback' => 'image_captcha_image',
    );
  }
  return $items;
}

/*
 * Implementation of hook_requirements()
 * @todo these checks should be for the install phase,
 * but this is not possible now for contributed modules (modules outside the
 * drupal/modules directory)
 */
function image_captcha_requirements($phase) {
  $requirements = array();
  $t = get_t();
  if ($phase == 'runtime') {
    if (function_exists('imagegd2')) {
      $gd_info = gd_info();
      if (!$gd_info['FreeType Support']) {
        $requirements['image_captcha_ft'] = array(
          'title' => $t('Image CAPTCHA'),
          'value' => $t('No FreeType support'),
          'description' => $t('FreeType support is required for working with TrueType fonts (.ttf), but the GD library for PHP does not support it.'),
          'severity' => REQUIREMENT_ERROR,
        );
      }
    }
    else {
      $requirements['image_captcha_gd'] = array(
        'title' => $t('Image CAPTCHA'),
        'value' => $t('No GD library'),
        'description' => $t('The GD library for PHP is missing or outdated. Please check the <a href="@url">PHP image documentation</a> for information on how to correct this.', array(
          '@url' => 'http://www.php.net/manual/en/ref.image.php',
        )),
        'severity' => REQUIREMENT_ERROR,
      );
    }
  }
  return $requirements;
}

/**
 * Returns:
 *  - the path to the image CAPTCHA font or FALSE when an error occured
 *  - error message
 */
function _image_captcha_get_font() {
  $font = variable_get('image_captcha_font', 'BUILTIN');
  $errmsg = FALSE;
  if ($font != 'BUILTIN' && (!is_file($font) || !is_readable($font))) {
    $errmsg = t('Could not find or read the configured font "%font" for the image CAPTCHA.', array(
      '%font' => $font,
    ));
    $font = FALSE;
  }
  return array(
    $font,
    $errmsg,
  );
}

/**
 * Configuration form for image_captcha
 * Implemented by _image_captcha_settings_form() in image_captcha.admin.inc
 */
function image_captcha_settings_form() {
  require_once drupal_get_path('module', 'image_captcha') . '/image_captcha.admin.inc';
  return _image_captcha_settings_form();
}

/**
 * Helper function for splitting an utf8 string correctly in characters.
 * Assumes the given utf8 string is well formed.
 * See http://en.wikipedia.org/wiki/Utf8 for more info
 */
function _image_captcha_utf8_split($str) {
  $characters = array();
  $len = strlen($str);
  for ($i = 0; $i < $len;) {
    $chr = ord($str[$i]);
    if (($chr & 0x80) == 0x0) {

      // one byte character (0zzzzzzz)
      $width = 1;
    }
    else {
      if (($chr & 0xe0) == 0xc0) {

        // two byte character (first byte: 110yyyyy)
        $width = 2;
      }
      elseif (($chr & 0xf0) == 0xe0) {

        // three byte character (first byte: 1110xxxx)
        $width = 3;
      }
      elseif (($chr & 0xf8) == 0xf0) {

        // four byte character (first byte: 11110www)
        $width = 4;
      }
      else {
        watchdog('CAPTCHA', t('Encountered an illegal byte while splitting an utf8 string in characters.'), WATCHDOG_ERROR);
        return $characters;
      }
    }
    $characters[] = substr($str, $i, $width);
    $i += $width;
  }
  return $characters;
}

/**
 * Implementation of hook_captcha().
 */
function image_captcha_captcha($op, $captcha_type = '', $response = '') {
  switch ($op) {
    case 'list':

      // only offer image CAPTCHA if possible to generate an image CAPTCHA
      list($font, $errmsg) = _image_captcha_get_font();
      if (function_exists('imagejpeg') && $font) {
        return array(
          'Image',
        );
      }
      else {
        return array();
      }
      break;
    case 'generate':
      if ($captcha_type == 'Image') {

        // In offline mode, the image CAPTCHA does not work because the request
        // for the image itself won't succeed (only ?q=user is permitted for
        // unauthenticated users). We fall back to the Math CAPTCHA in that case.
        global $user;
        if (variable_get('site_offline', FALSE) && $user->uid == 0) {
          return captcha_captcha('generate', 'Math');
        }

        // generate a CAPTCHA code
        $allowed_chars = _image_captcha_utf8_split(variable_get('image_captcha_image_allowed_chars', IMAGE_CAPTCHA_ALLOWED_CHARACTERS));
        $code_length = (int) variable_get('image_captcha_code_length', 5);
        $code = '';
        for ($i = 0; $i < $code_length; $i++) {
          $code .= $allowed_chars[array_rand($allowed_chars)];
        }

        // store the answer in $_SESSION for the image generator function (which happens in another http request)
        $seed = mt_rand();
        $_SESSION['image_captcha'][$seed] = $code;

        // build the result to return
        $result = array();

        // Handle the case insesitivity option and change the code to lower case
        // before saving it as solution.
        if (variable_get('image_captcha_case_insensitive', FALSE)) {
          $code = strtolower($code);
          $result['preprocess'] = TRUE;
          $description = t('Enter the characters shown in the image without spaces.');
        }
        else {
          $description = t('Enter the characters shown in the image without spaces, also respect upper and lower case.');
        }
        $result['solution'] = $code;

        // Create the image CAPTCHA form elements
        // The img markup isn't done with theme('image', ...) because that
        // function needs a path to a real file (not applicable)
        // or a full absolute URL (which requires to add protocol and domain)
        $result['form']['captcha_image'] = array(
          '#type' => 'markup',
          '#value' => '<img src="' . check_url(url("image_captcha/{$seed}")) . '" alt="' . t('Image CAPTCHA') . '" title="' . t('Image CAPTCHA') . '" />',
          '#weight' => -2,
        );
        $result['form']['captcha_response'] = array(
          '#type' => 'textfield',
          '#title' => t('What code is in the image?'),
          '#description' => $description,
          '#weight' => 0,
          '#required' => TRUE,
          '#size' => 15,
        );
        return $result;
      }
      break;
    case 'preprocess':

      // Preprocessing the response for case insesitive validation
      if ($captcha_type == 'Image') {
        return strtolower($response);
      }
      break;
  }
}

/**
 * menu callback function that generates the CAPTCHA image
 */
function image_captcha_image($seed = NULL) {
  if (!$seed) {
    return;
  }

  // Only generate captcha if code exists in the session.
  if (isset($_SESSION['image_captcha'][$seed])) {
    $code = $_SESSION['image_captcha'][$seed];

    // Unset the code from $_SESSION to prevent rerendering the CAPTCHA.
    unset($_SESSION['image_captcha'][$seed]);
    require_once drupal_get_path('module', 'image_captcha') . '/image_captcha.user.inc';

    // generate the image
    $image = @_image_captcha_generate_image($code);

    // check of generation was successful
    if (!$image) {
      watchdog('CAPTCHA', t('Generation of image CAPTCHA failed. Check your image CAPTCHA configuration and especially the used font.'), WATCHDOG_ERROR);
      exit;
    }

    // Send the image resource as an image to the client
    drupal_set_header("Content-type: image/jpeg");

    // Following header is needed for Konqueror, which would re-request the image
    // on a mouseover event, which failes because the image can only be generated
    // once. This cache directive forces Konqueror to use cache instead of
    // re-requesting
    drupal_set_header("Cache-Control: max-age=3600, must-revalidate");

    // print the image as jpg to the client
    imagejpeg($image);

    // Clean up
    imagedestroy($image);
    exit;
  }
}

/**
 * small helper function for parsing a hexadecimal color to a RGB tuple
 */
function _image_captcha_hex_to_rgb($hex) {

  // handle #RGB format
  if (strlen($hex) == 4) {
    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
  }
  $c = hexdec($hex);
  $rgb = array();
  for ($i = 16; $i >= 0; $i -= 8) {
    $rgb[] = $c >> $i & 0xff;
  }
  return $rgb;
}

Functions

Namesort descending Description
image_captcha_captcha Implementation of hook_captcha().
image_captcha_help Implementation of hook_help().
image_captcha_image menu callback function that generates the CAPTCHA image
image_captcha_menu Implementation of hook_menu().
image_captcha_requirements
image_captcha_settings_form Configuration form for image_captcha Implemented by _image_captcha_settings_form() in image_captcha.admin.inc
_image_captcha_get_font Returns:
_image_captcha_hex_to_rgb small helper function for parsing a hexadecimal color to a RGB tuple
_image_captcha_utf8_split Helper function for splitting an utf8 string correctly in characters. Assumes the given utf8 string is well formed. See http://en.wikipedia.org/wiki/Utf8 for more info

Constants

Namesort descending Description
IMAGE_CAPTCHA_ALLOWED_CHARACTERS @file Implementation of image CAPTCHA for use with the CAPTCHA module