PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Captcha/Image.php

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