'. 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.') .'

'; if (in_array('Image', image_captcha_captcha('list'))) { $result = image_captcha_captcha('generate', 'Image'); $img = $result['form']['captcha_image']['#value']; $output .= t('

Example image, generated with the current settings:

!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 PHP image documentation 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) == 0x00) { // 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' => ''. 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; }