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

/core/common/third_party/Zend/Captcha/Image.php

https://bitbucket.org/xperez/codeigniter-cross-modular-extensions-xhmvc
PHP | 632 lines | 288 code | 74 blank | 270 comment | 47 complexity | 5bd95890dbbd5ded75da63b29fa23ef7 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. * @throws Exception\ExtensionNotLoadedException
  111. */
  112. public function __construct($options = null)
  113. {
  114. if (!extension_loaded("gd")) {
  115. throw new Exception\ExtensionNotLoadedException("Image CAPTCHA requires GD extension");
  116. }
  117. if (!function_exists("imagepng")) {
  118. throw new Exception\ExtensionNotLoadedException("Image CAPTCHA requires PNG support");
  119. }
  120. if (!function_exists("imageftbbox")) {
  121. throw new Exception\ExtensionNotLoadedException("Image CAPTCHA requires FT fonts support");
  122. }
  123. parent::__construct($options);
  124. }
  125. /**
  126. * @return string
  127. */
  128. public function getImgAlt()
  129. {
  130. return $this->imgAlt;
  131. }
  132. /**
  133. * @return string
  134. */
  135. public function getStartImage()
  136. {
  137. return $this->startImage;
  138. }
  139. /**
  140. * @return int
  141. */
  142. public function getDotNoiseLevel()
  143. {
  144. return $this->dotNoiseLevel;
  145. }
  146. /**
  147. * @return int
  148. */
  149. public function getLineNoiseLevel()
  150. {
  151. return $this->lineNoiseLevel;
  152. }
  153. /**
  154. * Get captcha expiration
  155. *
  156. * @return int
  157. */
  158. public function getExpiration()
  159. {
  160. return $this->expiration;
  161. }
  162. /**
  163. * Get garbage collection frequency
  164. *
  165. * @return int
  166. */
  167. public function getGcFreq()
  168. {
  169. return $this->gcFreq;
  170. }
  171. /**
  172. * Get font to use when generating captcha
  173. *
  174. * @return string
  175. */
  176. public function getFont()
  177. {
  178. return $this->font;
  179. }
  180. /**
  181. * Get font size
  182. *
  183. * @return int
  184. */
  185. public function getFontSize()
  186. {
  187. return $this->fsize;
  188. }
  189. /**
  190. * Get captcha image height
  191. *
  192. * @return int
  193. */
  194. public function getHeight()
  195. {
  196. return $this->height;
  197. }
  198. /**
  199. * Get captcha image directory
  200. *
  201. * @return string
  202. */
  203. public function getImgDir()
  204. {
  205. return $this->imgDir;
  206. }
  207. /**
  208. * Get captcha image base URL
  209. *
  210. * @return string
  211. */
  212. public function getImgUrl()
  213. {
  214. return $this->imgUrl;
  215. }
  216. /**
  217. * Get captcha image file suffix
  218. *
  219. * @return string
  220. */
  221. public function getSuffix()
  222. {
  223. return $this->suffix;
  224. }
  225. /**
  226. * Get captcha image width
  227. *
  228. * @return int
  229. */
  230. public function getWidth()
  231. {
  232. return $this->width;
  233. }
  234. /**
  235. * @param string $startImage
  236. * @return Image
  237. */
  238. public function setStartImage($startImage)
  239. {
  240. $this->startImage = $startImage;
  241. return $this;
  242. }
  243. /**
  244. * @param int $dotNoiseLevel
  245. * @return Image
  246. */
  247. public function setDotNoiseLevel($dotNoiseLevel)
  248. {
  249. $this->dotNoiseLevel = $dotNoiseLevel;
  250. return $this;
  251. }
  252. /**
  253. * @param int $lineNoiseLevel
  254. * @return Image
  255. */
  256. public function setLineNoiseLevel($lineNoiseLevel)
  257. {
  258. $this->lineNoiseLevel = $lineNoiseLevel;
  259. return $this;
  260. }
  261. /**
  262. * Set captcha expiration
  263. *
  264. * @param int $expiration
  265. * @return Image
  266. */
  267. public function setExpiration($expiration)
  268. {
  269. $this->expiration = $expiration;
  270. return $this;
  271. }
  272. /**
  273. * Set garbage collection frequency
  274. *
  275. * @param int $gcFreq
  276. * @return Image
  277. */
  278. public function setGcFreq($gcFreq)
  279. {
  280. $this->gcFreq = $gcFreq;
  281. return $this;
  282. }
  283. /**
  284. * Set captcha font
  285. *
  286. * @param string $font
  287. * @return Image
  288. */
  289. public function setFont($font)
  290. {
  291. $this->font = $font;
  292. return $this;
  293. }
  294. /**
  295. * Set captcha font size
  296. *
  297. * @param int $fsize
  298. * @return Image
  299. */
  300. public function setFontSize($fsize)
  301. {
  302. $this->fsize = $fsize;
  303. return $this;
  304. }
  305. /**
  306. * Set captcha image height
  307. *
  308. * @param int $height
  309. * @return Image
  310. */
  311. public function setHeight($height)
  312. {
  313. $this->height = $height;
  314. return $this;
  315. }
  316. /**
  317. * Set captcha image storage directory
  318. *
  319. * @param string $imgDir
  320. * @return Image
  321. */
  322. public function setImgDir($imgDir)
  323. {
  324. $this->imgDir = rtrim($imgDir, "/\\") . '/';
  325. return $this;
  326. }
  327. /**
  328. * Set captcha image base URL
  329. *
  330. * @param string $imgUrl
  331. * @return Image
  332. */
  333. public function setImgUrl($imgUrl)
  334. {
  335. $this->imgUrl = rtrim($imgUrl, "/\\") . '/';
  336. return $this;
  337. }
  338. /**
  339. * @param string $imgAlt
  340. * @return Image
  341. */
  342. public function setImgAlt($imgAlt)
  343. {
  344. $this->imgAlt = $imgAlt;
  345. return $this;
  346. }
  347. /**
  348. * Set captcha image filename suffix
  349. *
  350. * @param string $suffix
  351. * @return Image
  352. */
  353. public function setSuffix($suffix)
  354. {
  355. $this->suffix = $suffix;
  356. return $this;
  357. }
  358. /**
  359. * Set captcha image width
  360. *
  361. * @param int $width
  362. * @return Image
  363. */
  364. public function setWidth($width)
  365. {
  366. $this->width = $width;
  367. return $this;
  368. }
  369. /**
  370. * Generate random frequency
  371. *
  372. * @return float
  373. */
  374. protected function randomFreq()
  375. {
  376. return mt_rand(700000, 1000000) / 15000000;
  377. }
  378. /**
  379. * Generate random phase
  380. *
  381. * @return float
  382. */
  383. protected function randomPhase()
  384. {
  385. // random phase from 0 to pi
  386. return mt_rand(0, 3141592) / 1000000;
  387. }
  388. /**
  389. * Generate random character size
  390. *
  391. * @return int
  392. */
  393. protected function randomSize()
  394. {
  395. return mt_rand(300, 700) / 100;
  396. }
  397. /**
  398. * Generate captcha
  399. *
  400. * @return string captcha ID
  401. */
  402. public function generate()
  403. {
  404. $id = parent::generate();
  405. $tries = 5;
  406. // If there's already such file, try creating a new ID
  407. while ($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) {
  408. $id = $this->generateRandomId();
  409. $this->setId($id);
  410. }
  411. $this->generateImage($id, $this->getWord());
  412. if (mt_rand(1, $this->getGcFreq()) == 1) {
  413. $this->gc();
  414. }
  415. return $id;
  416. }
  417. /**
  418. * Generate image captcha
  419. *
  420. * Override this function if you want different image generator
  421. * Wave transform from http://www.captcha.ru/captchas/multiwave/
  422. *
  423. * @param string $id Captcha ID
  424. * @param string $word Captcha word
  425. * @throws Exception\NoFontProvidedException if no font was set
  426. * @throws Exception\ImageNotLoadableException if start image cannot be loaded
  427. */
  428. protected function generateImage($id, $word)
  429. {
  430. $font = $this->getFont();
  431. if (empty($font)) {
  432. throw new Exception\NoFontProvidedException('Image CAPTCHA requires font');
  433. }
  434. $w = $this->getWidth();
  435. $h = $this->getHeight();
  436. $fsize = $this->getFontSize();
  437. $img_file = $this->getImgDir() . $id . $this->getSuffix();
  438. if (empty($this->startImage)) {
  439. $img = imagecreatetruecolor($w, $h);
  440. } else {
  441. // Potential error is change to exception
  442. ErrorHandler::start();
  443. $img = imagecreatefrompng($this->startImage);
  444. $error = ErrorHandler::stop();
  445. if (!$img || $error) {
  446. throw new Exception\ImageNotLoadableException(
  447. "Can not load start image '{$this->startImage}'", 0, $error
  448. );
  449. }
  450. $w = imagesx($img);
  451. $h = imagesy($img);
  452. }
  453. $text_color = imagecolorallocate($img, 0, 0, 0);
  454. $bg_color = imagecolorallocate($img, 255, 255, 255);
  455. imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color);
  456. $textbox = imageftbbox($fsize, 0, $font, $word);
  457. $x = ($w - ($textbox[2] - $textbox[0])) / 2;
  458. $y = ($h - ($textbox[7] - $textbox[1])) / 2;
  459. imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word);
  460. // generate noise
  461. for ($i=0; $i < $this->dotNoiseLevel; $i++) {
  462. imagefilledellipse($img, mt_rand(0, $w), mt_rand(0, $h), 2, 2, $text_color);
  463. }
  464. for ($i=0; $i < $this->lineNoiseLevel; $i++) {
  465. imageline($img, mt_rand(0, $w), mt_rand(0, $h), mt_rand(0, $w), mt_rand(0, $h), $text_color);
  466. }
  467. // transformed image
  468. $img2 = imagecreatetruecolor($w, $h);
  469. $bg_color = imagecolorallocate($img2, 255, 255, 255);
  470. imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color);
  471. // apply wave transforms
  472. $freq1 = $this->randomFreq();
  473. $freq2 = $this->randomFreq();
  474. $freq3 = $this->randomFreq();
  475. $freq4 = $this->randomFreq();
  476. $ph1 = $this->randomPhase();
  477. $ph2 = $this->randomPhase();
  478. $ph3 = $this->randomPhase();
  479. $ph4 = $this->randomPhase();
  480. $szx = $this->randomSize();
  481. $szy = $this->randomSize();
  482. for ($x = 0; $x < $w; $x++) {
  483. for ($y = 0; $y < $h; $y++) {
  484. $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx;
  485. $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy;
  486. if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) {
  487. continue;
  488. } else {
  489. $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF;
  490. $color_x = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF;
  491. $color_y = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF;
  492. $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF;
  493. }
  494. if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) {
  495. // ignore background
  496. continue;
  497. } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) {
  498. // transfer inside of the image as-is
  499. $newcolor = 0;
  500. } else {
  501. // do antialiasing for border items
  502. $frac_x = $sx-floor($sx);
  503. $frac_y = $sy-floor($sy);
  504. $frac_x1 = 1-$frac_x;
  505. $frac_y1 = 1-$frac_y;
  506. $newcolor = $color * $frac_x1 * $frac_y1
  507. + $color_x * $frac_x * $frac_y1
  508. + $color_y * $frac_x1 * $frac_y
  509. + $color_xy * $frac_x * $frac_y;
  510. }
  511. imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor));
  512. }
  513. }
  514. // generate noise
  515. for ($i=0; $i<$this->dotNoiseLevel; $i++) {
  516. imagefilledellipse($img2, mt_rand(0, $w), mt_rand(0, $h), 2, 2, $text_color);
  517. }
  518. for ($i=0; $i<$this->lineNoiseLevel; $i++) {
  519. imageline($img2, mt_rand(0, $w), mt_rand(0, $h), mt_rand(0, $w), mt_rand(0, $h), $text_color);
  520. }
  521. imagepng($img2, $img_file);
  522. imagedestroy($img);
  523. imagedestroy($img2);
  524. }
  525. /**
  526. * Remove old files from image directory
  527. *
  528. */
  529. protected function gc()
  530. {
  531. $expire = time() - $this->getExpiration();
  532. $imgdir = $this->getImgDir();
  533. if (!$imgdir || strlen($imgdir) < 2) {
  534. // safety guard
  535. return;
  536. }
  537. $suffixLength = strlen($this->suffix);
  538. foreach (new DirectoryIterator($imgdir) as $file) {
  539. if (!$file->isDot() && !$file->isDir()) {
  540. if (file_exists($file->getPathname()) && $file->getMTime() < $expire) {
  541. // only deletes files ending with $this->suffix
  542. if (substr($file->getFilename(), -($suffixLength)) == $this->suffix) {
  543. unlink($file->getPathname());
  544. }
  545. }
  546. }
  547. }
  548. }
  549. /**
  550. * Get helper name used to render captcha
  551. *
  552. * @return string
  553. */
  554. public function getHelperName()
  555. {
  556. return 'captcha/image';
  557. }
  558. }