<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Captcha * @subpackage Adapter * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** @see Zend_Captcha_Word */ require_once 'Zend/Captcha/Word.php'; /** * Image-based captcha element * * Generates image displaying random word * * @category Zend * @package Zend_Captcha * @subpackage Adapter * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Captcha_Image extends Zend_Captcha_Word { /** * Directory for generated images * * @var string */ protected $_imgDir = "./images/captcha/"; /** * URL for accessing images * * @var string */ protected $_imgUrl = "/images/captcha/"; /** * Image's alt tag content * * @var string */ protected $_imgAlt = ""; /** * Image suffix (including dot) * * @var string */ protected $_suffix = ".png"; /** * Image width * * @var int */ protected $_width = 200; /** * Image height * * @var int */ protected $_height = 50; /** * Font size * * @var int */ protected $_fsize = 24; /** * Image font file * * @var string */ protected $_font; /** * Image to use as starting point * Default is blank image. If provided, should be PNG image. * * @var string */ protected $_startImage; /** * How frequently to execute garbage collection * * @var int */ protected $_gcFreq = 10; /** * How long to keep generated images * * @var int */ protected $_expiration = 600; /** * Number of noise dots on image * Used twice - before and after transform * * @var int */ protected $_dotNoiseLevel = 100; /** * Number of noise lines on image * Used twice - before and after transform * * @var int */ protected $_lineNoiseLevel = 5; /** * @return string */ public function getImgAlt () { return $this->_imgAlt; } /** * @return string */ public function getStartImage () { return $this->_startImage; } /** * @return int */ public function getDotNoiseLevel () { return $this->_dotNoiseLevel; } /** * @return int */ public function getLineNoiseLevel () { return $this->_lineNoiseLevel; } /** * Get captcha expiration * * @return int */ public function getExpiration() { return $this->_expiration; } /** * Get garbage collection frequency * * @return int */ public function getGcFreq() { return $this->_gcFreq; } /** * Get font to use when generating captcha * * @return string */ public function getFont() { return $this->_font; } /** * Get font size * * @return int */ public function getFontSize() { return $this->_fsize; } /** * Get captcha image height * * @return int */ public function getHeight() { return $this->_height; } /** * Get captcha image directory * * @return string */ public function getImgDir() { return $this->_imgDir; } /** * Get captcha image base URL * * @return string */ public function getImgUrl() { return $this->_imgUrl; } /** * Get captcha image file suffix * * @return string */ public function getSuffix() { return $this->_suffix; } /** * Get captcha image width * * @return int */ public function getWidth() { return $this->_width; } /** * @param string $startImage */ public function setStartImage ($startImage) { $this->_startImage = $startImage; return $this; } /** * @param int $dotNoiseLevel */ public function setDotNoiseLevel ($dotNoiseLevel) { $this->_dotNoiseLevel = $dotNoiseLevel; return $this; } /** * @param int $lineNoiseLevel */ public function setLineNoiseLevel ($lineNoiseLevel) { $this->_lineNoiseLevel = $lineNoiseLevel; return $this; } /** * Set captcha expiration * * @param int $expiration * @return Zend_Captcha_Image */ public function setExpiration($expiration) { $this->_expiration = $expiration; return $this; } /** * Set garbage collection frequency * * @param int $gcFreq * @return Zend_Captcha_Image */ public function setGcFreq($gcFreq) { $this->_gcFreq = $gcFreq; return $this; } /** * Set captcha font * * @param string $font * @return Zend_Captcha_Image */ public function setFont($font) { $this->_font = $font; return $this; } /** * Set captcha font size * * @param int $fsize * @return Zend_Captcha_Image */ public function setFontSize($fsize) { $this->_fsize = $fsize; return $this; } /** * Set captcha image height * * @param int $height * @return Zend_Captcha_Image */ public function setHeight($height) { $this->_height = $height; return $this; } /** * Set captcha image storage directory * * @param string $imgDir * @return Zend_Captcha_Image */ public function setImgDir($imgDir) { $this->_imgDir = rtrim($imgDir, "/\\") . '/'; return $this; } /** * Set captcha image base URL * * @param string $imgUrl * @return Zend_Captcha_Image */ public function setImgUrl($imgUrl) { $this->_imgUrl = rtrim($imgUrl, "/\\") . '/'; return $this; } /** * @param string $imgAlt */ public function setImgAlt ($imgAlt) { $this->_imgAlt = $imgAlt; return $this; } /** * Set captch image filename suffix * * @param string $suffix * @return Zend_Captcha_Image */ public function setSuffix($suffix) { $this->_suffix = $suffix; return $this; } /** * Set captcha image width * * @param int $width * @return Zend_Captcha_Image */ public function setWidth($width) { $this->_width = $width; return $this; } /** * Generate random frequency * * @return float */ protected function _randomFreq() { return mt_rand(700000, 1000000) / 15000000; } /** * Generate random phase * * @return float */ protected function _randomPhase() { // random phase from 0 to pi return mt_rand(0, 3141592) / 1000000; } /** * Generate random character size * * @return int */ protected function _randomSize() { return mt_rand(300, 700) / 100; } /** * Generate captcha * * @return string captcha ID */ public function generate() { $id = parent::generate(); $tries = 5; // If there's already such file, try creating a new ID while($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) { $id = $this->_generateRandomId(); $this->_setId($id); } $this->_generateImage($id, $this->getWord()); if (mt_rand(1, $this->getGcFreq()) == 1) { $this->_gc(); } return $id; } /** * Generate image captcha * * Override this function if you want different image generator * Wave transform from http://www.captcha.ru/captchas/multiwave/ * * @param string $id Captcha ID * @param string $word Captcha word */ protected function _generateImage($id, $word) { if (!extension_loaded("gd")) { require_once 'Zend/Captcha/Exception.php'; throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension"); } if (!function_exists("imagepng")) { require_once 'Zend/Captcha/Exception.php'; throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support"); } if (!function_exists("imageftbbox")) { require_once 'Zend/Captcha/Exception.php'; throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support"); } $font = $this->getFont(); if (empty($font)) { require_once 'Zend/Captcha/Exception.php'; throw new Zend_Captcha_Exception("Image CAPTCHA requires font"); } $w = $this->getWidth(); $h = $this->getHeight(); $fsize = $this->getFontSize(); $img_file = $this->getImgDir() . $id . $this->getSuffix(); if(empty($this->_startImage)) { $img = imagecreatetruecolor($w, $h); } else { $img = imagecreatefrompng($this->_startImage); if(!$img) { require_once 'Zend/Captcha/Exception.php'; throw new Zend_Captcha_Exception("Can not load start image"); } $w = imagesx($img); $h = imagesy($img); } $text_color = imagecolorallocate($img, 0, 0, 0); $bg_color = imagecolorallocate($img, 255, 255, 255); imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color); $textbox = imageftbbox($fsize, 0, $font, $word); $x = ($w - ($textbox[2] - $textbox[0])) / 2; $y = ($h - ($textbox[7] - $textbox[1])) / 2; imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word); // generate noise for ($i=0; $i<$this->_dotNoiseLevel; $i++) { imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); } for($i=0; $i<$this->_lineNoiseLevel; $i++) { imageline($img, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); } // transformed image $img2 = imagecreatetruecolor($w, $h); $bg_color = imagecolorallocate($img2, 255, 255, 255); imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color); // apply wave transforms $freq1 = $this->_randomFreq(); $freq2 = $this->_randomFreq(); $freq3 = $this->_randomFreq(); $freq4 = $this->_randomFreq(); $ph1 = $this->_randomPhase(); $ph2 = $this->_randomPhase(); $ph3 = $this->_randomPhase(); $ph4 = $this->_randomPhase(); $szx = $this->_randomSize(); $szy = $this->_randomSize(); for ($x = 0; $x < $w; $x++) { for ($y = 0; $y < $h; $y++) { $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx; $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy; if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) { continue; } else { $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF; $color_x = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF; $color_y = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF; $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF; } if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) { // ignore background continue; } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) { // transfer inside of the image as-is $newcolor = 0; } else { // do antialiasing for border items $frac_x = $sx-floor($sx); $frac_y = $sy-floor($sy); $frac_x1 = 1-$frac_x; $frac_y1 = 1-$frac_y; $newcolor = $color * $frac_x1 * $frac_y1 + $color_x * $frac_x * $frac_y1 + $color_y * $frac_x1 * $frac_y + $color_xy * $frac_x * $frac_y; } imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor)); } } // generate noise for ($i=0; $i<$this->_dotNoiseLevel; $i++) { imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); } for ($i=0; $i<$this->_lineNoiseLevel; $i++) { imageline($img2, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); } imagepng($img2, $img_file); imagedestroy($img); imagedestroy($img2); } /** * Remove old files from image directory * */ protected function _gc() { $expire = time() - $this->getExpiration(); $imgdir = $this->getImgDir(); if(!$imgdir || strlen($imgdir) < 2) { // safety guard return; } $suffixLength = strlen($this->_suffix); foreach (new DirectoryIterator($imgdir) as $file) { if (!$file->isDot() && !$file->isDir()) { if (file_exists($file->getPathname()) && $file->getMTime() < $expire) { // only deletes files ending with $this->_suffix if (substr($file->getFilename(), -($suffixLength)) == $this->_suffix) { unlink($file->getPathname()); } } } } } /** * Display the captcha * * @param Zend_View_Interface $view * @param mixed $element * @return string */ public function render(Zend_View_Interface $view = null, $element = null) { $endTag = ' />'; if (($view instanceof Zend_View_Abstract) && !$view->doctype()->isXhtml()) { $endTag = '>'; } return '<img width="' . $this->getWidth() . '" height="' . $this->getHeight() . '" alt="' . $this->getImgAlt() . '" src="' . $this->getImgUrl() . $this->getId() . $this->getSuffix() . '"' . $endTag; } }