1 <?php
2 3 4 5 6
7
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
39
40
41
42 43 44 45 46
47
48
49
50
51
52
53
54
55
56
57 define('CAPTCHA_SESSION_ID', 'php_captcha');
58 define('CAPTCHA_WIDTH', 200);
59 define('CAPTCHA_HEIGHT', 50);
60 define('CAPTCHA_NUM_CHARS', 5);
61 define('CAPTCHA_NUM_LINES', 70);
62 define('CAPTCHA_CHAR_SHADOW', false);
63 define('CAPTCHA_OWNER_TEXT', '');
64 define('CAPTCHA_CHAR_SET', '');
65 define('CAPTCHA_CASE_INSENSITIVE', true);
66 define('CAPTCHA_BACKGROUND_IMAGES', '');
67 define('CAPTCHA_MIN_FONT_SIZE', 16);
68 define('CAPTCHA_MAX_FONT_SIZE', 24);
69 define('CAPTCHA_USE_COLOUR', false);
70 define('CAPTCHA_FILE_TYPE', 'jpeg');
71 define('CAPTCHA_FLITE_PATH', '/usr/bin/flite');
72 define('CAPTCHA_AUDIO_PATH', '/tmp/');
73
74
75
76
77
78 class PhpCaptcha {
79 var $oImage;
80 var $aFonts;
81 var $iWidth;
82 var $iHeight;
83 var $iNumChars;
84 var $iNumLines;
85 var $iSpacing;
86 var $bCharShadow;
87 var $sOwnerText;
88 var $aCharSet;
89 var $bCaseInsensitive;
90 var $vBackgroundImages;
91 var $iMinFontSize;
92 var $iMaxFontSize;
93 var $bUseColour;
94 var $sFileType;
95 var $sCode = '';
96
97 function PhpCaptcha(
98 $aFonts,
99 $iWidth = CAPTCHA_WIDTH,
100 $iHeight = CAPTCHA_HEIGHT
101 ) {
102
103 $this->aFonts = $aFonts;
104 $this->SetNumChars(CAPTCHA_NUM_CHARS);
105 $this->SetNumLines(CAPTCHA_NUM_LINES);
106 $this->DisplayShadow(CAPTCHA_CHAR_SHADOW);
107 $this->SetOwnerText(CAPTCHA_OWNER_TEXT);
108 $this->SetCharSet(CAPTCHA_CHAR_SET);
109 $this->CaseInsensitive(CAPTCHA_CASE_INSENSITIVE);
110 $this->SetBackgroundImages(CAPTCHA_BACKGROUND_IMAGES);
111 $this->SetMinFontSize(CAPTCHA_MIN_FONT_SIZE);
112 $this->SetMaxFontSize(CAPTCHA_MAX_FONT_SIZE);
113 $this->UseColour(CAPTCHA_USE_COLOUR);
114 $this->SetFileType(CAPTCHA_FILE_TYPE);
115 $this->SetWidth($iWidth);
116 $this->SetHeight($iHeight);
117 }
118
119 function CalculateSpacing() {
120 $this->iSpacing = (int)(($this->iWidth * 0.94) / $this->iNumChars);
121 }
122
123 function SetWidth($iWidth) {
124 $this->iWidth = $iWidth;
125 if ($this->iWidth > 500) $this->iWidth = 500;
126 $this->CalculateSpacing();
127 }
128
129 function SetHeight($iHeight) {
130 $this->iHeight = $iHeight;
131 if ($this->iHeight > 200) $this->iHeight = 200;
132 }
133
134 function SetNumChars($iNumChars) {
135 $this->iNumChars = $iNumChars;
136 $this->CalculateSpacing();
137 }
138
139 function SetNumLines($iNumLines) {
140 $this->iNumLines = $iNumLines;
141 }
142
143 function DisplayShadow($bCharShadow) {
144 $this->bCharShadow = $bCharShadow;
145 }
146
147 function SetOwnerText($sOwnerText) {
148 $this->sOwnerText = $sOwnerText;
149 }
150
151 function SetCharSet($vCharSet) {
152
153 if (is_array($vCharSet)) {
154 $this->aCharSet = $vCharSet;
155 } else {
156 if ($vCharSet != '') {
157
158 $aCharSet = explode(',', $vCharSet);
159
160
161 $this->aCharSet = array();
162
163
164 foreach ($aCharSet as $sCurrentItem) {
165
166 if (strlen($sCurrentItem) == 3) {
167
168 $aRange = explode('-', $sCurrentItem);
169
170
171 if (count($aRange) == 2 && $aRange[0] < $aRange[1]) {
172
173 $aRange = range($aRange[0], $aRange[1]);
174
175
176 $this->aCharSet = array_merge($this->aCharSet, $aRange);
177 }
178 } else {
179 $this->aCharSet[] = $sCurrentItem;
180 }
181 }
182 }
183 }
184 }
185
186 function CaseInsensitive($bCaseInsensitive) {
187 $this->bCaseInsensitive = $bCaseInsensitive;
188 }
189
190 function SetBackgroundImages($vBackgroundImages) {
191 $this->vBackgroundImages = $vBackgroundImages;
192 }
193
194 function SetMinFontSize($iMinFontSize) {
195 $this->iMinFontSize = $iMinFontSize;
196 }
197
198 function SetMaxFontSize($iMaxFontSize) {
199 $this->iMaxFontSize = $iMaxFontSize;
200 }
201
202 function UseColour($bUseColour) {
203 $this->bUseColour = $bUseColour;
204 }
205
206 function SetFileType($sFileType) {
207
208 if (in_array($sFileType, array('gif', 'png', 'jpeg'))) {
209 $this->sFileType = $sFileType;
210 } else {
211 $this->sFileType = 'jpeg';
212 }
213 }
214
215 function DrawLines() {
216 for ($i = 0; $i < $this->iNumLines; $i++) {
217
218 if ($this->bUseColour) {
219 $iLineColour = imagecolorallocate($this->oImage, rand(100, 250), rand(100, 250), rand(100, 250));
220 } else {
221 $iRandColour = rand(100, 250);
222 $iLineColour = imagecolorallocate($this->oImage, $iRandColour, $iRandColour, $iRandColour);
223 }
224
225
226 imageline($this->oImage, rand(0, $this->iWidth), rand(0, $this->iHeight), rand(0, $this->iWidth), rand(0, $this->iHeight), $iLineColour);
227 }
228 }
229
230 function DrawOwnerText() {
231
232 $iBlack = imagecolorallocate($this->oImage, 0, 0, 0);
233
234 $iOwnerTextHeight = imagefontheight(2);
235
236 $iLineHeight = $this->iHeight - $iOwnerTextHeight - 4;
237
238
239 imageline($this->oImage, 0, $iLineHeight, $this->iWidth, $iLineHeight, $iBlack);
240
241
242 imagestring($this->oImage, 2, 3, $this->iHeight - $iOwnerTextHeight - 3, $this->sOwnerText, $iBlack);
243
244
245 $this->iHeight = $this->iHeight - $iOwnerTextHeight - 5;
246 }
247
248 function GenerateCode() {
249
250 $this->sCode = '';
251
252
253 for ($i = 0; $i < $this->iNumChars; $i++) {
254 if (count($this->aCharSet) > 0) {
255
256 $this->sCode .= $this->aCharSet[array_rand($this->aCharSet)];
257 } else {
258
259 $this->sCode .= chr(rand(65, 90));
260 }
261 }
262
263
264 if ($this->bCaseInsensitive) {
265 $_SESSION[CAPTCHA_SESSION_ID] = strtoupper($this->sCode);
266 } else {
267 $_SESSION[CAPTCHA_SESSION_ID] = $this->sCode;
268 }
269 }
270
271 function DrawCharacters() {
272
273 for ($i = 0; $i < strlen($this->sCode); $i++) {
274
275 $sCurrentFont = $this->aFonts[array_rand($this->aFonts)];
276
277
278 if ($this->bUseColour) {
279 $iTextColour = imagecolorallocate($this->oImage, rand(0, 100), rand(0, 100), rand(0, 100));
280
281 if ($this->bCharShadow) {
282
283 $iShadowColour = imagecolorallocate($this->oImage, rand(0, 100), rand(0, 100), rand(0, 100));
284 }
285 } else {
286 $iRandColour = rand(0, 100);
287 $iTextColour = imagecolorallocate($this->oImage, $iRandColour, $iRandColour, $iRandColour);
288
289 if ($this->bCharShadow) {
290
291 $iRandColour = rand(0, 100);
292 $iShadowColour = imagecolorallocate($this->oImage, $iRandColour, $iRandColour, $iRandColour);
293 }
294 }
295
296
297 $iFontSize = rand($this->iMinFontSize, $this->iMaxFontSize);
298
299
300 $iAngle = rand(-30, 30);
301
302
303 $aCharDetails = imageftbbox($iFontSize, $iAngle, $sCurrentFont, $this->sCode[$i], array());
304
305
306 $iCharRight = max($aCharDetails[2],$aCharDetails[4],$aCharDetails[6]) - $aCharDetails[0];
307 $iCharLeft = min($aCharDetails[2],$aCharDetails[4],$aCharDetails[6]) - $aCharDetails[0];
308 $iX = $this->iWidth * 0.04 + $i * $this->iSpacing;
309 $xPos = ($i + 1) * $this->iSpacing;
310 if ($iX + $iCharRight > $xPos)
311 $iX = $xPos - $this->iSpacing;
312
313
314 if ($iX + $iCharLeft < 0)
315 $iX = $this->iSpacing/2;
316
317 $iCharHeight = $aCharDetails[3] - $aCharDetails[5];
318
319 $iY = 4+$this->iHeight / 2 + $iCharHeight / 4;
320
321
322
323 imagefttext($this->oImage, $iFontSize, $iAngle, $iX, $iY, $iTextColour, $sCurrentFont, $this->sCode[$i], array());
324
325 if ($this->bCharShadow) {
326 $iOffsetAngle = rand(-30, 30);
327
328 $iRandOffsetX = rand(-5, 5);
329 $iRandOffsetY = rand(-5, 5);
330
331 imagefttext($this->oImage, $iFontSize, $iOffsetAngle, $iX + $iRandOffsetX, $iY + $iRandOffsetY, $iShadowColour, $sCurrentFont, $this->sCode[$i], array());
332 }
333 }
334
335 }
336
337 function WriteFile($sFilename) {
338 if ($sFilename == '') {
339
340 header("Content-type: image/$this->sFileType");
341 }
342
343 switch ($this->sFileType) {
344 case 'gif':
345 $sFilename != '' ? imagegif($this->oImage, $sFilename) : imagegif($this->oImage);
346 break;
347 case 'png':
348 $sFilename != '' ? imagepng($this->oImage, $sFilename) : imagepng($this->oImage);
349 break;
350 default:
351 $sFilename != '' ? imagejpeg($this->oImage, $sFilename) : imagejpeg($this->oImage);
352 }
353 }
354
355 function Create($sFilename = '') {
356
357 if (!function_exists('imagecreate') || !function_exists("image$this->sFileType") || ($this->vBackgroundImages != '' && !function_exists('imagecreatetruecolor'))) {
358 return false;
359 }
360
361
362 if (is_array($this->vBackgroundImages) || $this->vBackgroundImages != '') {
363
364 $this->oImage = imagecreatetruecolor($this->iWidth, $this->iHeight);
365
366
367 if (is_array($this->vBackgroundImages)) {
368 $iRandImage = array_rand($this->vBackgroundImages);
369 $oBackgroundImage = imagecreatefromjpeg($this->vBackgroundImages[$iRandImage]);
370 } else {
371 $oBackgroundImage = imagecreatefromjpeg($this->vBackgroundImages);
372 }
373
374
375 imagecopy($this->oImage, $oBackgroundImage, 0, 0, 0, 0, $this->iWidth, $this->iHeight);
376
377
378 imagedestroy($oBackgroundImage);
379 } else {
380
381 $this->oImage = imagecreate($this->iWidth, $this->iHeight);
382 }
383
384
385 imagecolorallocate($this->oImage, 255,255, 255);
386
387
388 if ($this->sOwnerText != '') {
389 $this->DrawOwnerText();
390 }
391
392
393 if (!is_array($this->vBackgroundImages) && $this->vBackgroundImages == '') {
394 $this->DrawLines();
395 }
396
397 $this->GenerateCode();
398 $this->DrawCharacters();
399
400
401 $this->WriteFile($sFilename);
402
403
404 imagedestroy($this->oImage);
405
406 return true;
407 }
408
409
410 function Validate($sUserCode, $bCaseInsensitive = true) {
411 if ($bCaseInsensitive) {
412 $sUserCode = strtoupper($sUserCode);
413 }
414
415 if (!empty($_SESSION[CAPTCHA_SESSION_ID]) &&
416 ($sUserCode == $_SESSION[CAPTCHA_SESSION_ID] || substr($_SESSION[CAPTCHA_SESSION_ID],1) == $sUserCode)
417 ) {
418
419 unset($_SESSION[CAPTCHA_SESSION_ID]);
420
421 return true;
422 }
423
424 return false;
425 }
426 }
427
428
429 class AudioPhpCaptcha {
430 var $sFlitePath;
431 var $sAudioPath;
432 var $sCode;
433
434 function AudioPhpCaptcha(
435 $sFlitePath = CAPTCHA_FLITE_PATH,
436 $sAudioPath = CAPTCHA_AUDIO_PATH
437 ) {
438 $this->SetFlitePath($sFlitePath);
439 $this->SetAudioPath($sAudioPath);
440
441
442 if (isset($_SESSION[CAPTCHA_SESSION_ID])) {
443 $this->sCode = $_SESSION[CAPTCHA_SESSION_ID];
444 }
445 }
446
447 function SetFlitePath($sFlitePath) {
448 $this->sFlitePath = $sFlitePath;
449 }
450
451 function SetAudioPath($sAudioPath) {
452 $this->sAudioPath = $sAudioPath;
453 }
454
455 function Mask($sText) {
456 $iLength = strlen($sText);
457
458
459 $sFormattedText = '';
460 for ($i = 0; $i < $iLength; $i++) {
461
462 if ($i > 0 && $i < $iLength - 1) {
463 $sFormattedText .= ', ';
464 } elseif ($i == $iLength - 1) {
465 $sFormattedText .= ' and ';
466 }
467 $sFormattedText .= $sText[$i];
468 }
469
470 $aPhrases = array(
471 "The %1\$s characters are as follows: %2\$s",
472 "%2\$s, are the %1\$s letters",
473 "Here are the %1\$s characters: %2\$s",
474 "%1\$s characters are: %2\$s",
475 "%1\$s letters: %2\$s"
476 );
477
478 $iPhrase = array_rand($aPhrases);
479
480 return sprintf($aPhrases[$iPhrase], $iLength, $sFormattedText);
481 }
482
483 function Create() {
484 $sText = $this->Mask($this->sCode);
485 $sFile = md5($this->sCode.time());
486
487
488 shell_exec("$this->sFlitePath -t \"$sText\" -o $this->sAudioPath$sFile.wav");
489
490
491 header('Content-type: audio/x-wav');
492 header("Content-Disposition: attachment;filename=$sFile.wav");
493
494
495 echo file_get_contents("$this->sAudioPath$sFile.wav");
496
497
498 @unlink("$this->sAudioPath$sFile.wav");
499 }
500 }
501
502
503 class PhpCaptchaColour extends PhpCaptcha {
504 function PhpCaptchaColour($aFonts, $iWidth = CAPTCHA_WIDTH, $iHeight = CAPTCHA_HEIGHT) {
505
506 parent::PhpCaptcha($aFonts, $iWidth, $iHeight);
507
508
509 $this->UseColour(true);
510 }
511 }
512 ?>
513
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-