PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Captcha/Image.php

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