|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Captcha |
|
17 * @subpackage Adapter |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Image.php 22589 2010-07-16 20:51:51Z mikaelkael $ |
|
21 */ |
|
22 |
|
23 /** @see Zend_Captcha_Word */ |
|
24 require_once 'Zend/Captcha/Word.php'; |
|
25 |
|
26 /** |
|
27 * Image-based captcha element |
|
28 * |
|
29 * Generates image displaying random word |
|
30 * |
|
31 * @category Zend |
|
32 * @package Zend_Captcha |
|
33 * @subpackage Adapter |
|
34 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
35 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
36 */ |
|
37 class Zend_Captcha_Image extends Zend_Captcha_Word |
|
38 { |
|
39 /** |
|
40 * Directory for generated images |
|
41 * |
|
42 * @var string |
|
43 */ |
|
44 protected $_imgDir = "./images/captcha/"; |
|
45 |
|
46 /** |
|
47 * URL for accessing images |
|
48 * |
|
49 * @var string |
|
50 */ |
|
51 protected $_imgUrl = "/images/captcha/"; |
|
52 |
|
53 /** |
|
54 * Image's alt tag content |
|
55 * |
|
56 * @var string |
|
57 */ |
|
58 protected $_imgAlt = ""; |
|
59 |
|
60 /** |
|
61 * Image suffix (including dot) |
|
62 * |
|
63 * @var string |
|
64 */ |
|
65 protected $_suffix = ".png"; |
|
66 |
|
67 /** |
|
68 * Image width |
|
69 * |
|
70 * @var int |
|
71 */ |
|
72 protected $_width = 200; |
|
73 |
|
74 /** |
|
75 * Image height |
|
76 * |
|
77 * @var int |
|
78 */ |
|
79 protected $_height = 50; |
|
80 |
|
81 /** |
|
82 * Font size |
|
83 * |
|
84 * @var int |
|
85 */ |
|
86 protected $_fsize = 24; |
|
87 |
|
88 /** |
|
89 * Image font file |
|
90 * |
|
91 * @var string |
|
92 */ |
|
93 protected $_font; |
|
94 |
|
95 /** |
|
96 * Image to use as starting point |
|
97 * Default is blank image. If provided, should be PNG image. |
|
98 * |
|
99 * @var string |
|
100 */ |
|
101 protected $_startImage; |
|
102 /** |
|
103 * How frequently to execute garbage collection |
|
104 * |
|
105 * @var int |
|
106 */ |
|
107 protected $_gcFreq = 10; |
|
108 |
|
109 /** |
|
110 * How long to keep generated images |
|
111 * |
|
112 * @var int |
|
113 */ |
|
114 protected $_expiration = 600; |
|
115 |
|
116 /** |
|
117 * Number of noise dots on image |
|
118 * Used twice - before and after transform |
|
119 * |
|
120 * @var int |
|
121 */ |
|
122 protected $_dotNoiseLevel = 100; |
|
123 /** |
|
124 * Number of noise lines on image |
|
125 * Used twice - before and after transform |
|
126 * |
|
127 * @var int |
|
128 */ |
|
129 protected $_lineNoiseLevel = 5; |
|
130 /** |
|
131 * @return string |
|
132 */ |
|
133 public function getImgAlt () |
|
134 { |
|
135 return $this->_imgAlt; |
|
136 } |
|
137 /** |
|
138 * @return string |
|
139 */ |
|
140 public function getStartImage () |
|
141 { |
|
142 return $this->_startImage; |
|
143 } |
|
144 /** |
|
145 * @return int |
|
146 */ |
|
147 public function getDotNoiseLevel () |
|
148 { |
|
149 return $this->_dotNoiseLevel; |
|
150 } |
|
151 /** |
|
152 * @return int |
|
153 */ |
|
154 public function getLineNoiseLevel () |
|
155 { |
|
156 return $this->_lineNoiseLevel; |
|
157 } |
|
158 /** |
|
159 * Get captcha expiration |
|
160 * |
|
161 * @return int |
|
162 */ |
|
163 public function getExpiration() |
|
164 { |
|
165 return $this->_expiration; |
|
166 } |
|
167 |
|
168 /** |
|
169 * Get garbage collection frequency |
|
170 * |
|
171 * @return int |
|
172 */ |
|
173 public function getGcFreq() |
|
174 { |
|
175 return $this->_gcFreq; |
|
176 } |
|
177 /** |
|
178 * Get font to use when generating captcha |
|
179 * |
|
180 * @return string |
|
181 */ |
|
182 public function getFont() |
|
183 { |
|
184 return $this->_font; |
|
185 } |
|
186 |
|
187 /** |
|
188 * Get font size |
|
189 * |
|
190 * @return int |
|
191 */ |
|
192 public function getFontSize() |
|
193 { |
|
194 return $this->_fsize; |
|
195 } |
|
196 |
|
197 /** |
|
198 * Get captcha image height |
|
199 * |
|
200 * @return int |
|
201 */ |
|
202 public function getHeight() |
|
203 { |
|
204 return $this->_height; |
|
205 } |
|
206 |
|
207 /** |
|
208 * Get captcha image directory |
|
209 * |
|
210 * @return string |
|
211 */ |
|
212 public function getImgDir() |
|
213 { |
|
214 return $this->_imgDir; |
|
215 } |
|
216 /** |
|
217 * Get captcha image base URL |
|
218 * |
|
219 * @return string |
|
220 */ |
|
221 public function getImgUrl() |
|
222 { |
|
223 return $this->_imgUrl; |
|
224 } |
|
225 /** |
|
226 * Get captcha image file suffix |
|
227 * |
|
228 * @return string |
|
229 */ |
|
230 public function getSuffix() |
|
231 { |
|
232 return $this->_suffix; |
|
233 } |
|
234 /** |
|
235 * Get captcha image width |
|
236 * |
|
237 * @return int |
|
238 */ |
|
239 public function getWidth() |
|
240 { |
|
241 return $this->_width; |
|
242 } |
|
243 /** |
|
244 * @param string $startImage |
|
245 */ |
|
246 public function setStartImage ($startImage) |
|
247 { |
|
248 $this->_startImage = $startImage; |
|
249 return $this; |
|
250 } |
|
251 /** |
|
252 * @param int $dotNoiseLevel |
|
253 */ |
|
254 public function setDotNoiseLevel ($dotNoiseLevel) |
|
255 { |
|
256 $this->_dotNoiseLevel = $dotNoiseLevel; |
|
257 return $this; |
|
258 } |
|
259 /** |
|
260 * @param int $lineNoiseLevel |
|
261 */ |
|
262 public function setLineNoiseLevel ($lineNoiseLevel) |
|
263 { |
|
264 $this->_lineNoiseLevel = $lineNoiseLevel; |
|
265 return $this; |
|
266 } |
|
267 |
|
268 /** |
|
269 * Set captcha expiration |
|
270 * |
|
271 * @param int $expiration |
|
272 * @return Zend_Captcha_Image |
|
273 */ |
|
274 public function setExpiration($expiration) |
|
275 { |
|
276 $this->_expiration = $expiration; |
|
277 return $this; |
|
278 } |
|
279 |
|
280 /** |
|
281 * Set garbage collection frequency |
|
282 * |
|
283 * @param int $gcFreq |
|
284 * @return Zend_Captcha_Image |
|
285 */ |
|
286 public function setGcFreq($gcFreq) |
|
287 { |
|
288 $this->_gcFreq = $gcFreq; |
|
289 return $this; |
|
290 } |
|
291 |
|
292 /** |
|
293 * Set captcha font |
|
294 * |
|
295 * @param string $font |
|
296 * @return Zend_Captcha_Image |
|
297 */ |
|
298 public function setFont($font) |
|
299 { |
|
300 $this->_font = $font; |
|
301 return $this; |
|
302 } |
|
303 |
|
304 /** |
|
305 * Set captcha font size |
|
306 * |
|
307 * @param int $fsize |
|
308 * @return Zend_Captcha_Image |
|
309 */ |
|
310 public function setFontSize($fsize) |
|
311 { |
|
312 $this->_fsize = $fsize; |
|
313 return $this; |
|
314 } |
|
315 |
|
316 /** |
|
317 * Set captcha image height |
|
318 * |
|
319 * @param int $height |
|
320 * @return Zend_Captcha_Image |
|
321 */ |
|
322 public function setHeight($height) |
|
323 { |
|
324 $this->_height = $height; |
|
325 return $this; |
|
326 } |
|
327 |
|
328 /** |
|
329 * Set captcha image storage directory |
|
330 * |
|
331 * @param string $imgDir |
|
332 * @return Zend_Captcha_Image |
|
333 */ |
|
334 public function setImgDir($imgDir) |
|
335 { |
|
336 $this->_imgDir = rtrim($imgDir, "/\\") . '/'; |
|
337 return $this; |
|
338 } |
|
339 |
|
340 /** |
|
341 * Set captcha image base URL |
|
342 * |
|
343 * @param string $imgUrl |
|
344 * @return Zend_Captcha_Image |
|
345 */ |
|
346 public function setImgUrl($imgUrl) |
|
347 { |
|
348 $this->_imgUrl = rtrim($imgUrl, "/\\") . '/'; |
|
349 return $this; |
|
350 } |
|
351 /** |
|
352 * @param string $imgAlt |
|
353 */ |
|
354 public function setImgAlt ($imgAlt) |
|
355 { |
|
356 $this->_imgAlt = $imgAlt; |
|
357 return $this; |
|
358 } |
|
359 |
|
360 /** |
|
361 * Set captch image filename suffix |
|
362 * |
|
363 * @param string $suffix |
|
364 * @return Zend_Captcha_Image |
|
365 */ |
|
366 public function setSuffix($suffix) |
|
367 { |
|
368 $this->_suffix = $suffix; |
|
369 return $this; |
|
370 } |
|
371 |
|
372 /** |
|
373 * Set captcha image width |
|
374 * |
|
375 * @param int $width |
|
376 * @return Zend_Captcha_Image |
|
377 */ |
|
378 public function setWidth($width) |
|
379 { |
|
380 $this->_width = $width; |
|
381 return $this; |
|
382 } |
|
383 |
|
384 /** |
|
385 * Generate random frequency |
|
386 * |
|
387 * @return float |
|
388 */ |
|
389 protected function _randomFreq() |
|
390 { |
|
391 return mt_rand(700000, 1000000) / 15000000; |
|
392 } |
|
393 |
|
394 /** |
|
395 * Generate random phase |
|
396 * |
|
397 * @return float |
|
398 */ |
|
399 protected function _randomPhase() |
|
400 { |
|
401 // random phase from 0 to pi |
|
402 return mt_rand(0, 3141592) / 1000000; |
|
403 } |
|
404 |
|
405 /** |
|
406 * Generate random character size |
|
407 * |
|
408 * @return int |
|
409 */ |
|
410 protected function _randomSize() |
|
411 { |
|
412 return mt_rand(300, 700) / 100; |
|
413 } |
|
414 |
|
415 /** |
|
416 * Generate captcha |
|
417 * |
|
418 * @return string captcha ID |
|
419 */ |
|
420 public function generate() |
|
421 { |
|
422 $id = parent::generate(); |
|
423 $tries = 5; |
|
424 // If there's already such file, try creating a new ID |
|
425 while($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) { |
|
426 $id = $this->_generateRandomId(); |
|
427 $this->_setId($id); |
|
428 } |
|
429 $this->_generateImage($id, $this->getWord()); |
|
430 |
|
431 if (mt_rand(1, $this->getGcFreq()) == 1) { |
|
432 $this->_gc(); |
|
433 } |
|
434 return $id; |
|
435 } |
|
436 |
|
437 /** |
|
438 * Generate image captcha |
|
439 * |
|
440 * Override this function if you want different image generator |
|
441 * Wave transform from http://www.captcha.ru/captchas/multiwave/ |
|
442 * |
|
443 * @param string $id Captcha ID |
|
444 * @param string $word Captcha word |
|
445 */ |
|
446 protected function _generateImage($id, $word) |
|
447 { |
|
448 if (!extension_loaded("gd")) { |
|
449 require_once 'Zend/Captcha/Exception.php'; |
|
450 throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension"); |
|
451 } |
|
452 |
|
453 if (!function_exists("imagepng")) { |
|
454 require_once 'Zend/Captcha/Exception.php'; |
|
455 throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support"); |
|
456 } |
|
457 |
|
458 if (!function_exists("imageftbbox")) { |
|
459 require_once 'Zend/Captcha/Exception.php'; |
|
460 throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support"); |
|
461 } |
|
462 |
|
463 $font = $this->getFont(); |
|
464 |
|
465 if (empty($font)) { |
|
466 require_once 'Zend/Captcha/Exception.php'; |
|
467 throw new Zend_Captcha_Exception("Image CAPTCHA requires font"); |
|
468 } |
|
469 |
|
470 $w = $this->getWidth(); |
|
471 $h = $this->getHeight(); |
|
472 $fsize = $this->getFontSize(); |
|
473 |
|
474 $img_file = $this->getImgDir() . $id . $this->getSuffix(); |
|
475 if(empty($this->_startImage)) { |
|
476 $img = imagecreatetruecolor($w, $h); |
|
477 } else { |
|
478 $img = imagecreatefrompng($this->_startImage); |
|
479 if(!$img) { |
|
480 require_once 'Zend/Captcha/Exception.php'; |
|
481 throw new Zend_Captcha_Exception("Can not load start image"); |
|
482 } |
|
483 $w = imagesx($img); |
|
484 $h = imagesy($img); |
|
485 } |
|
486 $text_color = imagecolorallocate($img, 0, 0, 0); |
|
487 $bg_color = imagecolorallocate($img, 255, 255, 255); |
|
488 imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color); |
|
489 $textbox = imageftbbox($fsize, 0, $font, $word); |
|
490 $x = ($w - ($textbox[2] - $textbox[0])) / 2; |
|
491 $y = ($h - ($textbox[7] - $textbox[1])) / 2; |
|
492 imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word); |
|
493 |
|
494 // generate noise |
|
495 for ($i=0; $i<$this->_dotNoiseLevel; $i++) { |
|
496 imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); |
|
497 } |
|
498 for($i=0; $i<$this->_lineNoiseLevel; $i++) { |
|
499 imageline($img, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); |
|
500 } |
|
501 |
|
502 // transformed image |
|
503 $img2 = imagecreatetruecolor($w, $h); |
|
504 $bg_color = imagecolorallocate($img2, 255, 255, 255); |
|
505 imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color); |
|
506 // apply wave transforms |
|
507 $freq1 = $this->_randomFreq(); |
|
508 $freq2 = $this->_randomFreq(); |
|
509 $freq3 = $this->_randomFreq(); |
|
510 $freq4 = $this->_randomFreq(); |
|
511 |
|
512 $ph1 = $this->_randomPhase(); |
|
513 $ph2 = $this->_randomPhase(); |
|
514 $ph3 = $this->_randomPhase(); |
|
515 $ph4 = $this->_randomPhase(); |
|
516 |
|
517 $szx = $this->_randomSize(); |
|
518 $szy = $this->_randomSize(); |
|
519 |
|
520 for ($x = 0; $x < $w; $x++) { |
|
521 for ($y = 0; $y < $h; $y++) { |
|
522 $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx; |
|
523 $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy; |
|
524 |
|
525 if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) { |
|
526 continue; |
|
527 } else { |
|
528 $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF; |
|
529 $color_x = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF; |
|
530 $color_y = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF; |
|
531 $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF; |
|
532 } |
|
533 if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) { |
|
534 // ignore background |
|
535 continue; |
|
536 } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) { |
|
537 // transfer inside of the image as-is |
|
538 $newcolor = 0; |
|
539 } else { |
|
540 // do antialiasing for border items |
|
541 $frac_x = $sx-floor($sx); |
|
542 $frac_y = $sy-floor($sy); |
|
543 $frac_x1 = 1-$frac_x; |
|
544 $frac_y1 = 1-$frac_y; |
|
545 |
|
546 $newcolor = $color * $frac_x1 * $frac_y1 |
|
547 + $color_x * $frac_x * $frac_y1 |
|
548 + $color_y * $frac_x1 * $frac_y |
|
549 + $color_xy * $frac_x * $frac_y; |
|
550 } |
|
551 imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor)); |
|
552 } |
|
553 } |
|
554 |
|
555 // generate noise |
|
556 for ($i=0; $i<$this->_dotNoiseLevel; $i++) { |
|
557 imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); |
|
558 } |
|
559 for ($i=0; $i<$this->_lineNoiseLevel; $i++) { |
|
560 imageline($img2, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); |
|
561 } |
|
562 |
|
563 imagepng($img2, $img_file); |
|
564 imagedestroy($img); |
|
565 imagedestroy($img2); |
|
566 } |
|
567 |
|
568 /** |
|
569 * Remove old files from image directory |
|
570 * |
|
571 */ |
|
572 protected function _gc() |
|
573 { |
|
574 $expire = time() - $this->getExpiration(); |
|
575 $imgdir = $this->getImgDir(); |
|
576 if(!$imgdir || strlen($imgdir) < 2) { |
|
577 // safety guard |
|
578 return; |
|
579 } |
|
580 $suffixLength = strlen($this->_suffix); |
|
581 foreach (new DirectoryIterator($imgdir) as $file) { |
|
582 if (!$file->isDot() && !$file->isDir()) { |
|
583 if ($file->getMTime() < $expire) { |
|
584 // only deletes files ending with $this->_suffix |
|
585 if (substr($file->getFilename(), -($suffixLength)) == $this->_suffix) { |
|
586 unlink($file->getPathname()); |
|
587 } |
|
588 } |
|
589 } |
|
590 } |
|
591 } |
|
592 |
|
593 /** |
|
594 * Display the captcha |
|
595 * |
|
596 * @param Zend_View_Interface $view |
|
597 * @param mixed $element |
|
598 * @return string |
|
599 */ |
|
600 public function render(Zend_View_Interface $view = null, $element = null) |
|
601 { |
|
602 return '<img width="' . $this->getWidth() . '" height="' . $this->getHeight() . '" alt="' . $this->getImgAlt() |
|
603 . '" src="' . $this->getImgUrl() . $this->getId() . $this->getSuffix() . '" />'; |
|
604 } |
|
605 } |