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