image_captcha.module

Tracking 5.x-3.x branch
  1. drupal
    1. 5 contributions/captcha/image_captcha/image_captcha.module
    2. 6 contributions/captcha/image_captcha/image_captcha.module
    3. 7 contributions/captcha/image_captcha/image_captcha.module

Implementation of image CAPTCHA for use with the CAPTCHA module

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

Constants

NameDescription
IMAGE_CAPTCHA_ALLOWED_CHARACTERS

Functions & methods

NameDescription
image_captcha_captchaImplementation of hook_captcha().
image_captcha_helpImplementation of hook_help().
image_captcha_imagemenu callback function that generates the CAPTCHA image
image_captcha_menuImplementation of hook_menu().
image_captcha_requirements
image_captcha_settings_formConfiguration form for image_captcha Implemented by _image_captcha_settings_form() in image_captcha.admin.inc
_image_captcha_get_fontReturns:
_image_captcha_hex_to_rgbsmall helper function for parsing a hexadecimal color to a RGB tuple
_image_captcha_utf8_splitHelper 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

File

View source
  1. <?php
  2. /**
  3. * @file Implementation of image CAPTCHA for use with the CAPTCHA module
  4. *
  5. * Loosely Based on MyCaptcha by Heine Deelstra
  6. * (http://heine.familiedeelstra.com/mycaptcha-download)
  7. *
  8. */
  9. define('IMAGE_CAPTCHA_ALLOWED_CHARACTERS', 'aAbBCdEeFfGHhijKLMmNPQRrSTtWXYZ23456789%$#!@+?*');
  10. /**
  11. * Implementation of hook_help().
  12. */
  13. function image_captcha_help($section) {
  14. switch ($section) {
  15. case 'admin/user/captcha/image_captcha':
  16. $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>';
  17. if (in_array('Image', image_captcha_captcha('list'))) {
  18. $result = image_captcha_captcha('generate', 'Image');
  19. $img = $result['form']['captcha_image']['#value'];
  20. $output .= t('<p>Example image, generated with the current settings:</p>!img', array('!img' => $img));
  21. }
  22. return $output;
  23. }
  24. }
  25. /**
  26. * Implementation of hook_menu().
  27. */
  28. function image_captcha_menu($may_cache) {
  29. $items = array();
  30. if ($may_cache) {
  31. // add an administration tab for image_captcha
  32. $items[] = array(
  33. 'path' => 'admin/user/captcha/image_captcha',
  34. 'title' => t('Image CAPTCHA'),
  35. 'callback' => 'drupal_get_form',
  36. 'callback arguments' => array('image_captcha_settings_form'),
  37. 'type' => MENU_LOCAL_TASK,
  38. );
  39. // callback for generating an image
  40. $items[] = array(
  41. 'path' => 'image_captcha',
  42. 'type' => MENU_CALLBACK,
  43. 'access' => TRUE,
  44. 'callback' => 'image_captcha_image',
  45. );
  46. }
  47. return $items;
  48. }
  49. /*
  50. * Implementation of hook_requirements()
  51. * @todo these checks should be for the install phase,
  52. * but this is not possible now for contributed modules (modules outside the
  53. * drupal/modules directory)
  54. */
  55. function image_captcha_requirements($phase) {
  56. $requirements = array();
  57. $t = get_t();
  58. if ($phase == 'runtime') {
  59. if (function_exists('imagegd2')) {
  60. $gd_info = gd_info();
  61. if (!$gd_info['FreeType Support']) {
  62. $requirements['image_captcha_ft'] = array(
  63. 'title' => $t('Image CAPTCHA'),
  64. 'value' => $t('No FreeType support'),
  65. 'description' => $t('FreeType support is required for working with TrueType fonts (.ttf), but the GD library for PHP does not support it.'),
  66. 'severity' => REQUIREMENT_ERROR,
  67. );
  68. }
  69. }
  70. else {
  71. $requirements['image_captcha_gd'] = array(
  72. 'title' => $t('Image CAPTCHA'),
  73. 'value' => $t('No GD library'),
  74. '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')),
  75. 'severity' => REQUIREMENT_ERROR,
  76. );
  77. }
  78. }
  79. return $requirements;
  80. }
  81. /**
  82. * Returns:
  83. * - the path to the image CAPTCHA font or FALSE when an error occured
  84. * - error message
  85. */
  86. function _image_captcha_get_font() {
  87. $font = variable_get('image_captcha_font', 'BUILTIN');
  88. $errmsg = FALSE;
  89. if ($font != 'BUILTIN' && (!is_file($font) || !is_readable($font))) {
  90. $errmsg = t('Could not find or read the configured font "%font" for the image CAPTCHA.', array('%font' => $font));
  91. $font = FALSE;
  92. }
  93. return array($font, $errmsg);
  94. }
  95. /**
  96. * Configuration form for image_captcha
  97. * Implemented by _image_captcha_settings_form() in image_captcha.admin.inc
  98. */
  99. function image_captcha_settings_form() {
  100. require_once(drupal_get_path('module', 'image_captcha') . '/image_captcha.admin.inc');
  101. return _image_captcha_settings_form();
  102. }
  103. /**
  104. * Helper function for splitting an utf8 string correctly in characters.
  105. * Assumes the given utf8 string is well formed.
  106. * See http://en.wikipedia.org/wiki/Utf8 for more info
  107. */
  108. function _image_captcha_utf8_split($str) {
  109. $characters = array();
  110. $len = strlen($str);
  111. for ($i=0; $i < $len; ) {
  112. $chr = ord($str[$i]);
  113. if (($chr & 0x80) == 0x00) { // one byte character (0zzzzzzz)
  114. $width = 1;
  115. }
  116. else {
  117. if (($chr & 0xE0) == 0xC0) { // two byte character (first byte: 110yyyyy)
  118. $width = 2;
  119. }
  120. elseif (($chr & 0xF0) == 0xE0) { // three byte character (first byte: 1110xxxx)
  121. $width = 3;
  122. }
  123. elseif (($chr & 0xF8) == 0xF0) { // four byte character (first byte: 11110www)
  124. $width = 4;
  125. }
  126. else {
  127. watchdog('CAPTCHA', t('Encountered an illegal byte while splitting an utf8 string in characters.'), WATCHDOG_ERROR);
  128. return $characters;
  129. }
  130. }
  131. $characters[] = substr($str, $i, $width);
  132. $i += $width;
  133. }
  134. return $characters;
  135. }
  136. /**
  137. * Implementation of hook_captcha().
  138. */
  139. function image_captcha_captcha($op, $captcha_type='', $response='') {
  140. switch ($op) {
  141. case 'list':
  142. // only offer image CAPTCHA if possible to generate an image CAPTCHA
  143. list($font, $errmsg) = _image_captcha_get_font();
  144. if (function_exists('imagejpeg') && $font) {
  145. return array('Image');
  146. }
  147. else {
  148. return array();
  149. }
  150. break;
  151. case 'generate':
  152. if ($captcha_type == 'Image') {
  153. // In offline mode, the image CAPTCHA does not work because the request
  154. // for the image itself won't succeed (only ?q=user is permitted for
  155. // unauthenticated users). We fall back to the Math CAPTCHA in that case.
  156. global $user;
  157. if (variable_get('site_offline', FALSE) && $user->uid == 0) {
  158. return captcha_captcha('generate', 'Math');
  159. }
  160. // generate a CAPTCHA code
  161. $allowed_chars = _image_captcha_utf8_split(variable_get('image_captcha_image_allowed_chars', IMAGE_CAPTCHA_ALLOWED_CHARACTERS));
  162. $code_length = (int)variable_get('image_captcha_code_length', 5);
  163. $code = '';
  164. for ($i = 0; $i < $code_length; $i++) {
  165. $code .= $allowed_chars[array_rand($allowed_chars)];
  166. }
  167. // store the answer in $_SESSION for the image generator function (which happens in another http request)
  168. $seed = mt_rand();
  169. $_SESSION['image_captcha'][$seed] = $code;
  170. // build the result to return
  171. $result = array();
  172. // Handle the case insesitivity option and change the code to lower case
  173. // before saving it as solution.
  174. if (variable_get('image_captcha_case_insensitive', FALSE)) {
  175. $code = strtolower($code);
  176. $result['preprocess'] = TRUE;
  177. $description = t('Enter the characters shown in the image without spaces.');
  178. }
  179. else {
  180. $description = t('Enter the characters shown in the image without spaces, also respect upper and lower case.');
  181. }
  182. $result['solution'] = $code;
  183. // Create the image CAPTCHA form elements
  184. // The img markup isn't done with theme('image', ...) because that
  185. // function needs a path to a real file (not applicable)
  186. // or a full absolute URL (which requires to add protocol and domain)
  187. $result['form']['captcha_image'] = array(
  188. '#type' => 'markup',
  189. '#value' => '<img src="'. check_url(url("image_captcha/$seed")) .'" alt="'. t('Image CAPTCHA') .'" title="'. t('Image CAPTCHA') .'" />',
  190. '#weight' => -2,
  191. );
  192. $result['form']['captcha_response'] = array(
  193. '#type' => 'textfield',
  194. '#title' => t('What code is in the image?'),
  195. '#description' => $description,
  196. '#weight' => 0,
  197. '#required' => TRUE,
  198. '#size' => 15,
  199. );
  200. return $result;
  201. }
  202. break;
  203. case 'preprocess':
  204. // Preprocessing the response for case insesitive validation
  205. if ($captcha_type == 'Image') {
  206. return strtolower($response);
  207. }
  208. break;
  209. }
  210. }
  211. /**
  212. * menu callback function that generates the CAPTCHA image
  213. */
  214. function image_captcha_image($seed=NULL) {
  215. if (!$seed) {
  216. return;
  217. }
  218. // Only generate captcha if code exists in the session.
  219. if (isset($_SESSION['image_captcha'][$seed])) {
  220. $code = $_SESSION['image_captcha'][$seed];
  221. // Unset the code from $_SESSION to prevent rerendering the CAPTCHA.
  222. unset($_SESSION['image_captcha'][$seed]);
  223. require_once(drupal_get_path('module', 'image_captcha') . '/image_captcha.user.inc');
  224. // generate the image
  225. $image = @_image_captcha_generate_image($code);
  226. // check of generation was successful
  227. if (!$image) {
  228. watchdog('CAPTCHA', t('Generation of image CAPTCHA failed. Check your image CAPTCHA configuration and especially the used font.'), WATCHDOG_ERROR);
  229. exit();
  230. }
  231. // Send the image resource as an image to the client
  232. drupal_set_header("Content-type: image/jpeg");
  233. // Following header is needed for Konqueror, which would re-request the image
  234. // on a mouseover event, which failes because the image can only be generated
  235. // once. This cache directive forces Konqueror to use cache instead of
  236. // re-requesting
  237. drupal_set_header("Cache-Control: max-age=3600, must-revalidate");
  238. // print the image as jpg to the client
  239. imagejpeg($image);
  240. // Clean up
  241. imagedestroy($image);
  242. exit();
  243. }
  244. }
  245. /**
  246. * small helper function for parsing a hexadecimal color to a RGB tuple
  247. */
  248. function _image_captcha_hex_to_rgb($hex) {
  249. // handle #RGB format
  250. if (strlen($hex) == 4) {
  251. $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
  252. }
  253. $c = hexdec($hex);
  254. $rgb = array();
  255. for ($i = 16; $i >= 0; $i -= 8) {
  256. $rgb[] = ($c >> $i) & 0xFF;
  257. }
  258. return $rgb;
  259. }