PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/zendframework/zendframework/library/Zend/Captcha/Image.php

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