danielebarchiesi@2
|
1 <?php
|
danielebarchiesi@2
|
2
|
danielebarchiesi@2
|
3 /**
|
danielebarchiesi@2
|
4 * @file
|
danielebarchiesi@2
|
5 * Implements image CAPTCHA for use with the CAPTCHA module
|
danielebarchiesi@2
|
6 */
|
danielebarchiesi@2
|
7
|
danielebarchiesi@2
|
8 define('IMAGE_CAPTCHA_ALLOWED_CHARACTERS', 'aAbBCdEeFfGHhijKLMmNPQRrSTtWXYZ23456789');
|
danielebarchiesi@2
|
9
|
danielebarchiesi@2
|
10 // Setup status flags.
|
danielebarchiesi@2
|
11 define('IMAGE_CAPTCHA_ERROR_NO_GDLIB', 1);
|
danielebarchiesi@2
|
12 define('IMAGE_CAPTCHA_ERROR_NO_TTF_SUPPORT', 2);
|
danielebarchiesi@2
|
13 define('IMAGE_CAPTCHA_ERROR_TTF_FILE_READ_PROBLEM', 4);
|
danielebarchiesi@2
|
14
|
danielebarchiesi@2
|
15 define('IMAGE_CAPTCHA_FILE_FORMAT_JPG', 1);
|
danielebarchiesi@2
|
16 define('IMAGE_CAPTCHA_FILE_FORMAT_PNG', 2);
|
danielebarchiesi@2
|
17 define('IMAGE_CAPTCHA_FILE_FORMAT_TRANSPARENT_PNG', 3);
|
danielebarchiesi@2
|
18
|
danielebarchiesi@2
|
19
|
danielebarchiesi@2
|
20 /**
|
danielebarchiesi@2
|
21 * Implements hook_help().
|
danielebarchiesi@2
|
22 */
|
danielebarchiesi@2
|
23 function image_captcha_help($path, $arg) {
|
danielebarchiesi@2
|
24 switch ($path) {
|
danielebarchiesi@2
|
25 case 'admin/config/people/captcha/image_captcha':
|
danielebarchiesi@2
|
26 $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>';
|
danielebarchiesi@2
|
27 return $output;
|
danielebarchiesi@2
|
28 }
|
danielebarchiesi@2
|
29 }
|
danielebarchiesi@2
|
30
|
danielebarchiesi@2
|
31 /**
|
danielebarchiesi@2
|
32 * Implements hook_menu().
|
danielebarchiesi@2
|
33 */
|
danielebarchiesi@2
|
34 function image_captcha_menu() {
|
danielebarchiesi@2
|
35 $items = array();
|
danielebarchiesi@2
|
36 // add an administration tab for image_captcha
|
danielebarchiesi@2
|
37 $items['admin/config/people/captcha/image_captcha'] = array(
|
danielebarchiesi@2
|
38 'title' => 'Image CAPTCHA',
|
danielebarchiesi@2
|
39 'file' => 'image_captcha.admin.inc',
|
danielebarchiesi@2
|
40 'page callback' => 'drupal_get_form',
|
danielebarchiesi@2
|
41 'page arguments' => array('image_captcha_settings_form'),
|
danielebarchiesi@2
|
42 'access arguments' => array('administer CAPTCHA settings'),
|
danielebarchiesi@2
|
43 'type' => MENU_LOCAL_TASK,
|
danielebarchiesi@2
|
44 );
|
danielebarchiesi@2
|
45 // Menu path for generating font example.
|
danielebarchiesi@2
|
46 $items['admin/config/people/captcha/image_captcha/font_preview'] = array(
|
danielebarchiesi@2
|
47 'title' => 'Font example',
|
danielebarchiesi@2
|
48 'file' => 'image_captcha.admin.inc',
|
danielebarchiesi@2
|
49 'page callback' => 'image_captcha_font_preview',
|
danielebarchiesi@2
|
50 'access arguments' => array('administer CAPTCHA settings'),
|
danielebarchiesi@2
|
51 'type' => MENU_CALLBACK,
|
danielebarchiesi@2
|
52 );
|
danielebarchiesi@2
|
53 // callback for generating an image
|
danielebarchiesi@2
|
54 $items['image_captcha'] = array(
|
danielebarchiesi@2
|
55 'file' => 'image_captcha.user.inc',
|
danielebarchiesi@2
|
56 'page callback' => 'image_captcha_image',
|
danielebarchiesi@2
|
57 'access callback' => TRUE,
|
danielebarchiesi@2
|
58 'type' => MENU_CALLBACK,
|
danielebarchiesi@2
|
59 );
|
danielebarchiesi@2
|
60 return $items;
|
danielebarchiesi@2
|
61 }
|
danielebarchiesi@2
|
62
|
danielebarchiesi@2
|
63 /**
|
danielebarchiesi@2
|
64 * Helper function for getting the fonts to use in the image CAPTCHA.
|
danielebarchiesi@2
|
65 *
|
danielebarchiesi@2
|
66 * @return a list of font paths.
|
danielebarchiesi@2
|
67 */
|
danielebarchiesi@2
|
68 function _image_captcha_get_enabled_fonts() {
|
danielebarchiesi@2
|
69 if (IMAGE_CAPTCHA_ERROR_NO_TTF_SUPPORT & _image_captcha_check_setup(FALSE)) {
|
danielebarchiesi@2
|
70 return array('BUILTIN');
|
danielebarchiesi@2
|
71 }
|
danielebarchiesi@2
|
72 else {
|
danielebarchiesi@2
|
73 $default = array(
|
danielebarchiesi@2
|
74 drupal_get_path('module', 'image_captcha') . '/fonts/Tesox/tesox.ttf',
|
danielebarchiesi@2
|
75 drupal_get_path('module', 'image_captcha') . '/fonts/Tuffy/Tuffy.ttf',
|
danielebarchiesi@2
|
76 );
|
danielebarchiesi@2
|
77 return variable_get('image_captcha_fonts', $default);
|
danielebarchiesi@2
|
78 }
|
danielebarchiesi@2
|
79 }
|
danielebarchiesi@2
|
80
|
danielebarchiesi@2
|
81 /**
|
danielebarchiesi@2
|
82 * Helper function for checking if the specified fonts are available.
|
danielebarchiesi@2
|
83 *
|
danielebarchiesi@2
|
84 * @param $fonts paths of fonts to check.
|
danielebarchiesi@2
|
85 * @return list($readable_fonts, $problem_fonts)
|
danielebarchiesi@2
|
86 */
|
danielebarchiesi@2
|
87 function _image_captcha_check_fonts($fonts) {
|
danielebarchiesi@2
|
88 $readable_fonts = array();
|
danielebarchiesi@2
|
89 $problem_fonts = array();
|
danielebarchiesi@2
|
90 foreach ($fonts as $font) {
|
danielebarchiesi@2
|
91 if ($font != 'BUILTIN' && (!is_file($font) || !is_readable($font))) {
|
danielebarchiesi@2
|
92 $problem_fonts[] = $font;
|
danielebarchiesi@2
|
93 }
|
danielebarchiesi@2
|
94 else {
|
danielebarchiesi@2
|
95 $readable_fonts[] = $font;
|
danielebarchiesi@2
|
96 }
|
danielebarchiesi@2
|
97 }
|
danielebarchiesi@2
|
98 return array($readable_fonts, $problem_fonts);
|
danielebarchiesi@2
|
99 }
|
danielebarchiesi@2
|
100
|
danielebarchiesi@2
|
101 /**
|
danielebarchiesi@2
|
102 * Helper function for splitting an utf8 string correctly in characters.
|
danielebarchiesi@2
|
103 * Assumes the given utf8 string is well formed.
|
danielebarchiesi@2
|
104 * See http://en.wikipedia.org/wiki/Utf8 for more info
|
danielebarchiesi@2
|
105 */
|
danielebarchiesi@2
|
106 function _image_captcha_utf8_split($str) {
|
danielebarchiesi@2
|
107 $characters = array();
|
danielebarchiesi@2
|
108 $len = strlen($str);
|
danielebarchiesi@2
|
109 for ($i=0; $i < $len; ) {
|
danielebarchiesi@2
|
110 $chr = ord($str[$i]);
|
danielebarchiesi@2
|
111 if (($chr & 0x80) == 0x00) { // one byte character (0zzzzzzz)
|
danielebarchiesi@2
|
112 $width = 1;
|
danielebarchiesi@2
|
113 }
|
danielebarchiesi@2
|
114 else {
|
danielebarchiesi@2
|
115 if (($chr & 0xE0) == 0xC0) { // two byte character (first byte: 110yyyyy)
|
danielebarchiesi@2
|
116 $width = 2;
|
danielebarchiesi@2
|
117 }
|
danielebarchiesi@2
|
118 elseif (($chr & 0xF0) == 0xE0) { // three byte character (first byte: 1110xxxx)
|
danielebarchiesi@2
|
119 $width = 3;
|
danielebarchiesi@2
|
120 }
|
danielebarchiesi@2
|
121 elseif (($chr & 0xF8) == 0xF0) { // four byte character (first byte: 11110www)
|
danielebarchiesi@2
|
122 $width = 4;
|
danielebarchiesi@2
|
123 }
|
danielebarchiesi@2
|
124 else {
|
danielebarchiesi@2
|
125 watchdog('CAPTCHA', 'Encountered an illegal byte while splitting an utf8 string in characters.', array(), WATCHDOG_ERROR);
|
danielebarchiesi@2
|
126 return $characters;
|
danielebarchiesi@2
|
127 }
|
danielebarchiesi@2
|
128 }
|
danielebarchiesi@2
|
129 $characters[] = substr($str, $i, $width);
|
danielebarchiesi@2
|
130 $i += $width;
|
danielebarchiesi@2
|
131 }
|
danielebarchiesi@2
|
132 return $characters;
|
danielebarchiesi@2
|
133 }
|
danielebarchiesi@2
|
134
|
danielebarchiesi@2
|
135 /**
|
danielebarchiesi@2
|
136 * Helper function for checking the setup of the Image CAPTCHA.
|
danielebarchiesi@2
|
137 *
|
danielebarchiesi@2
|
138 * The image CAPTCHA requires at least the GD PHP library.
|
danielebarchiesi@2
|
139 * Support for TTF is recommended and the enabled
|
danielebarchiesi@2
|
140 * font files should be readable.
|
danielebarchiesi@2
|
141 * This functions checks these things.
|
danielebarchiesi@2
|
142 *
|
danielebarchiesi@2
|
143 * @param $check_fonts whether or not the enabled fonts should be checked.
|
danielebarchiesi@2
|
144 *
|
danielebarchiesi@2
|
145 * @return status code: bitwise 'OR' of status flags like
|
danielebarchiesi@2
|
146 * IMAGE_CAPTCHA_ERROR_NO_GDLIB, IMAGE_CAPTCHA_ERROR_NO_TTF_SUPPORT,
|
danielebarchiesi@2
|
147 * IMAGE_CAPTCHA_ERROR_TTF_FILE_READ_PROBLEM.
|
danielebarchiesi@2
|
148 */
|
danielebarchiesi@2
|
149 function _image_captcha_check_setup($check_fonts=TRUE) {
|
danielebarchiesi@2
|
150 // Start clean.
|
danielebarchiesi@2
|
151 $status = 0;
|
danielebarchiesi@2
|
152 // Check if we can use the GD library.
|
danielebarchiesi@2
|
153 // We need at least the imagepng function (for font previews on the settings page).
|
danielebarchiesi@2
|
154 // Note that the imagejpg function is optionally also used, but not required.
|
danielebarchiesi@2
|
155 if (!function_exists('imagepng')) {
|
danielebarchiesi@2
|
156 $status = $status | IMAGE_CAPTCHA_ERROR_NO_GDLIB;
|
danielebarchiesi@2
|
157 }
|
danielebarchiesi@2
|
158 if (!function_exists('imagettftext')) {
|
danielebarchiesi@2
|
159 $status = $status | IMAGE_CAPTCHA_ERROR_NO_TTF_SUPPORT;
|
danielebarchiesi@2
|
160 }
|
danielebarchiesi@2
|
161 if ($check_fonts) {
|
danielebarchiesi@2
|
162 // Check availability of enabled fonts.
|
danielebarchiesi@2
|
163 $fonts = _image_captcha_get_enabled_fonts();
|
danielebarchiesi@2
|
164 list($readable_fonts, $problem_fonts) = _image_captcha_check_fonts($fonts);
|
danielebarchiesi@2
|
165 if (count($problem_fonts) != 0) {
|
danielebarchiesi@2
|
166 $status = $status | IMAGE_CAPTCHA_ERROR_TTF_FILE_READ_PROBLEM;
|
danielebarchiesi@2
|
167 }
|
danielebarchiesi@2
|
168 }
|
danielebarchiesi@2
|
169 return $status;
|
danielebarchiesi@2
|
170 }
|
danielebarchiesi@2
|
171
|
danielebarchiesi@2
|
172 /**
|
danielebarchiesi@2
|
173 * Helper function for calculating image height and width
|
danielebarchiesi@2
|
174 * based on given code and current font/spacing settings.
|
danielebarchiesi@2
|
175 *
|
danielebarchiesi@2
|
176 * @return array($width, $heigh)
|
danielebarchiesi@2
|
177 */
|
danielebarchiesi@2
|
178 function _image_captcha_image_size($code) {
|
danielebarchiesi@2
|
179 // Get settings
|
danielebarchiesi@2
|
180 $font_size = (int) variable_get('image_captcha_font_size', 30);
|
danielebarchiesi@2
|
181 $character_spacing = (float) variable_get('image_captcha_character_spacing', '1.2');
|
danielebarchiesi@2
|
182 $characters = _image_captcha_utf8_split($code);
|
danielebarchiesi@2
|
183 $character_quantity = count($characters);
|
danielebarchiesi@2
|
184
|
danielebarchiesi@2
|
185 // Calculate height and width
|
danielebarchiesi@2
|
186 $width = $character_spacing * $font_size * $character_quantity;
|
danielebarchiesi@2
|
187 $height = 2 * $font_size;
|
danielebarchiesi@2
|
188
|
danielebarchiesi@2
|
189 return array($width, $height);
|
danielebarchiesi@2
|
190 }
|
danielebarchiesi@2
|
191
|
danielebarchiesi@2
|
192
|
danielebarchiesi@2
|
193 /**
|
danielebarchiesi@2
|
194 * Implements hook_captcha().
|
danielebarchiesi@2
|
195 */
|
danielebarchiesi@2
|
196 function image_captcha_captcha($op, $captcha_type='', $captcha_sid=NULL) {
|
danielebarchiesi@2
|
197 switch ($op) {
|
danielebarchiesi@2
|
198 case 'list':
|
danielebarchiesi@2
|
199 // Only offer the image CAPTCHA if it is possible to generate an image on this setup.
|
danielebarchiesi@2
|
200 if (!(_image_captcha_check_setup() & IMAGE_CAPTCHA_ERROR_NO_GDLIB)) {
|
danielebarchiesi@2
|
201 return array('Image');
|
danielebarchiesi@2
|
202 }
|
danielebarchiesi@2
|
203 else {
|
danielebarchiesi@2
|
204 return array();
|
danielebarchiesi@2
|
205 }
|
danielebarchiesi@2
|
206 break;
|
danielebarchiesi@2
|
207
|
danielebarchiesi@2
|
208 case 'generate':
|
danielebarchiesi@2
|
209 if ($captcha_type == 'Image') {
|
danielebarchiesi@2
|
210 // In maintenance mode, the image CAPTCHA does not work because the request
|
danielebarchiesi@2
|
211 // for the image itself won't succeed (only ?q=user is permitted for
|
danielebarchiesi@2
|
212 // unauthenticated users). We fall back to the Math CAPTCHA in that case.
|
danielebarchiesi@2
|
213 global $user;
|
danielebarchiesi@2
|
214 if (variable_get('maintenance_mode', 0) && $user->uid == 0) {
|
danielebarchiesi@2
|
215 return captcha_captcha('generate', 'Math');
|
danielebarchiesi@2
|
216 }
|
danielebarchiesi@2
|
217 // generate a CAPTCHA code
|
danielebarchiesi@2
|
218 $allowed_chars = _image_captcha_utf8_split(variable_get('image_captcha_image_allowed_chars', IMAGE_CAPTCHA_ALLOWED_CHARACTERS));
|
danielebarchiesi@2
|
219 $code_length = (int)variable_get('image_captcha_code_length', 5);
|
danielebarchiesi@2
|
220 $code = '';
|
danielebarchiesi@2
|
221 for ($i = 0; $i < $code_length; $i++) {
|
danielebarchiesi@2
|
222 $code .= $allowed_chars[array_rand($allowed_chars)];
|
danielebarchiesi@2
|
223 }
|
danielebarchiesi@2
|
224
|
danielebarchiesi@2
|
225 // build the result to return
|
danielebarchiesi@2
|
226 $result = array();
|
danielebarchiesi@2
|
227
|
danielebarchiesi@2
|
228 $result['solution'] = $code;
|
danielebarchiesi@2
|
229 // Generate image source URL (add timestamp to avoid problems with
|
danielebarchiesi@2
|
230 // client side caching: subsequent images of the same CAPTCHA session
|
danielebarchiesi@2
|
231 // have the same URL, but should display a different code).
|
danielebarchiesi@2
|
232 $options = array(
|
danielebarchiesi@2
|
233 'query' => array(
|
danielebarchiesi@2
|
234 'sid' => $captcha_sid,
|
danielebarchiesi@2
|
235 'ts' => REQUEST_TIME,
|
danielebarchiesi@2
|
236 ),
|
danielebarchiesi@2
|
237 );
|
danielebarchiesi@2
|
238 $img_src = check_url(url("image_captcha", $options));
|
danielebarchiesi@2
|
239 list($width, $height) = _image_captcha_image_size($code);
|
danielebarchiesi@2
|
240 // TODO: start using a theming funtion for generating the image markup?
|
danielebarchiesi@2
|
241 $result['form']['captcha_image'] = array(
|
danielebarchiesi@2
|
242 '#type' => 'markup',
|
danielebarchiesi@2
|
243 '#markup' => '<img src="' . $img_src
|
danielebarchiesi@2
|
244 . '" width="'. $width . '" height="' . $height
|
danielebarchiesi@2
|
245 . '" alt="' . t('Image CAPTCHA') . '" title="' . t('Image CAPTCHA') . '" />',
|
danielebarchiesi@2
|
246 '#weight' => -2,
|
danielebarchiesi@2
|
247 );
|
danielebarchiesi@2
|
248 $result['form']['captcha_response'] = array(
|
danielebarchiesi@2
|
249 '#type' => 'textfield',
|
danielebarchiesi@2
|
250 '#title' => t('What code is in the image?'),
|
danielebarchiesi@2
|
251 '#description' => t('Enter the characters shown in the image.'),
|
danielebarchiesi@2
|
252 '#weight' => 0,
|
danielebarchiesi@2
|
253 '#required' => TRUE,
|
danielebarchiesi@2
|
254 '#size' => 15,
|
danielebarchiesi@2
|
255 );
|
danielebarchiesi@2
|
256
|
danielebarchiesi@2
|
257 // Handle the case insensitive validation option combined with ignoring spaces.
|
danielebarchiesi@2
|
258 switch (variable_get('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE)) {
|
danielebarchiesi@2
|
259 case CAPTCHA_DEFAULT_VALIDATION_CASE_SENSITIVE:
|
danielebarchiesi@2
|
260 $result['captcha_validate'] = 'captcha_validate_ignore_spaces';
|
danielebarchiesi@2
|
261 break;
|
danielebarchiesi@2
|
262 case CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE:
|
danielebarchiesi@2
|
263 $result['captcha_validate'] = 'captcha_validate_case_insensitive_ignore_spaces';
|
danielebarchiesi@2
|
264 break;
|
danielebarchiesi@2
|
265 }
|
danielebarchiesi@2
|
266
|
danielebarchiesi@2
|
267 return $result;
|
danielebarchiesi@2
|
268 }
|
danielebarchiesi@2
|
269 break;
|
danielebarchiesi@2
|
270
|
danielebarchiesi@2
|
271 }
|
danielebarchiesi@2
|
272 }
|