File indexing completed on 2024-05-12 06:02:19

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Captcha
0017  * @subpackage Adapter
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** @see Zend_Captcha_Word */
0024 // require_once 'Zend/Captcha/Word.php';
0025 
0026 /**
0027  * Image-based captcha element
0028  *
0029  * Generates image displaying random word
0030  *
0031  * @category   Zend
0032  * @package    Zend_Captcha
0033  * @subpackage Adapter
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  */
0037 class Zend_Captcha_Image extends Zend_Captcha_Word
0038 {
0039     /**
0040      * Directory for generated images
0041      *
0042      * @var string
0043      */
0044     protected $_imgDir = "./images/captcha/";
0045 
0046     /**
0047      * URL for accessing images
0048      *
0049      * @var string
0050      */
0051     protected $_imgUrl = "/images/captcha/";
0052 
0053     /**
0054      * Image's alt tag content
0055      *
0056      * @var string
0057      */
0058     protected $_imgAlt = "";
0059 
0060     /**
0061      * Image suffix (including dot)
0062      *
0063      * @var string
0064      */
0065     protected $_suffix = ".png";
0066 
0067     /**
0068      * Image width
0069      *
0070      * @var int
0071      */
0072     protected $_width = 200;
0073 
0074     /**
0075      * Image height
0076      *
0077      * @var int
0078      */
0079     protected $_height = 50;
0080 
0081     /**
0082      * Font size
0083      *
0084      * @var int
0085      */
0086     protected $_fsize = 24;
0087 
0088     /**
0089      * Image font file
0090      *
0091      * @var string
0092      */
0093     protected $_font;
0094 
0095     /**
0096      * Image to use as starting point
0097      * Default is blank image. If provided, should be PNG image.
0098      *
0099      * @var string
0100      */
0101     protected $_startImage;
0102     /**
0103      * How frequently to execute garbage collection
0104      *
0105      * @var int
0106      */
0107     protected $_gcFreq = 10;
0108 
0109     /**
0110      * How long to keep generated images
0111      *
0112      * @var int
0113      */
0114     protected $_expiration = 600;
0115 
0116     /**
0117      * Number of noise dots on image
0118      * Used twice - before and after transform
0119      *
0120      * @var int
0121      */
0122     protected $_dotNoiseLevel = 100;
0123     /**
0124      * Number of noise lines on image
0125      * Used twice - before and after transform
0126      *
0127      * @var int
0128      */
0129     protected $_lineNoiseLevel = 5;
0130     /**
0131      * @return string
0132      */
0133     public function getImgAlt ()
0134     {
0135         return $this->_imgAlt;
0136     }
0137     /**
0138      * @return string
0139      */
0140     public function getStartImage ()
0141     {
0142         return $this->_startImage;
0143     }
0144     /**
0145      * @return int
0146      */
0147     public function getDotNoiseLevel ()
0148     {
0149         return $this->_dotNoiseLevel;
0150     }
0151     /**
0152      * @return int
0153      */
0154     public function getLineNoiseLevel ()
0155     {
0156         return $this->_lineNoiseLevel;
0157     }
0158     /**
0159      * Get captcha expiration
0160      *
0161      * @return int
0162      */
0163     public function getExpiration()
0164     {
0165         return $this->_expiration;
0166     }
0167 
0168     /**
0169      * Get garbage collection frequency
0170      *
0171      * @return int
0172      */
0173     public function getGcFreq()
0174     {
0175         return $this->_gcFreq;
0176     }
0177     /**
0178      * Get font to use when generating captcha
0179      *
0180      * @return string
0181      */
0182     public function getFont()
0183     {
0184         return $this->_font;
0185     }
0186 
0187     /**
0188      * Get font size
0189      *
0190      * @return int
0191      */
0192     public function getFontSize()
0193     {
0194         return $this->_fsize;
0195     }
0196 
0197     /**
0198      * Get captcha image height
0199      *
0200      * @return int
0201      */
0202     public function getHeight()
0203     {
0204         return $this->_height;
0205     }
0206 
0207     /**
0208      * Get captcha image directory
0209      *
0210      * @return string
0211      */
0212     public function getImgDir()
0213     {
0214         return $this->_imgDir;
0215     }
0216     /**
0217      * Get captcha image base URL
0218      *
0219      * @return string
0220      */
0221     public function getImgUrl()
0222     {
0223         return $this->_imgUrl;
0224     }
0225     /**
0226      * Get captcha image file suffix
0227      *
0228      * @return string
0229      */
0230     public function getSuffix()
0231     {
0232         return $this->_suffix;
0233     }
0234     /**
0235      * Get captcha image width
0236      *
0237      * @return int
0238      */
0239     public function getWidth()
0240     {
0241         return $this->_width;
0242     }
0243 
0244     /**
0245      * Set start image
0246      *
0247      * @param string $startImage
0248      * @return Zend_Captcha_Image
0249      */
0250     public function setStartImage ($startImage)
0251     {
0252         $this->_startImage = $startImage;
0253         return $this;
0254     }
0255 
0256     /**
0257      * Set dot noise level
0258      *
0259      * @param int $dotNoiseLevel
0260      * @return Zend_Captcha_Image
0261      */
0262     public function setDotNoiseLevel ($dotNoiseLevel)
0263     {
0264         $this->_dotNoiseLevel = $dotNoiseLevel;
0265         return $this;
0266     }
0267 
0268     /**
0269      * Set line noise level
0270      *
0271      * @param int $lineNoiseLevel
0272      * @return Zend_Captcha_Image
0273      */
0274     public function setLineNoiseLevel ($lineNoiseLevel)
0275     {
0276         $this->_lineNoiseLevel = $lineNoiseLevel;
0277         return $this;
0278     }
0279 
0280     /**
0281      * Set captcha expiration
0282      *
0283      * @param int $expiration
0284      * @return Zend_Captcha_Image
0285      */
0286     public function setExpiration($expiration)
0287     {
0288         $this->_expiration = $expiration;
0289         return $this;
0290     }
0291 
0292     /**
0293      * Set garbage collection frequency
0294      *
0295      * @param int $gcFreq
0296      * @return Zend_Captcha_Image
0297      */
0298     public function setGcFreq($gcFreq)
0299     {
0300         $this->_gcFreq = $gcFreq;
0301         return $this;
0302     }
0303 
0304     /**
0305      * Set captcha font
0306      *
0307      * @param  string $font
0308      * @return Zend_Captcha_Image
0309      */
0310     public function setFont($font)
0311     {
0312         $this->_font = $font;
0313         return $this;
0314     }
0315 
0316     /**
0317      * Set captcha font size
0318      *
0319      * @param  int $fsize
0320      * @return Zend_Captcha_Image
0321      */
0322     public function setFontSize($fsize)
0323     {
0324         $this->_fsize = $fsize;
0325         return $this;
0326     }
0327 
0328     /**
0329      * Set captcha image height
0330      *
0331      * @param  int $height
0332      * @return Zend_Captcha_Image
0333      */
0334     public function setHeight($height)
0335     {
0336         $this->_height = $height;
0337         return $this;
0338     }
0339 
0340     /**
0341      * Set captcha image storage directory
0342      *
0343      * @param  string $imgDir
0344      * @return Zend_Captcha_Image
0345      */
0346     public function setImgDir($imgDir)
0347     {
0348         $this->_imgDir = rtrim($imgDir, "/\\") . '/';
0349         return $this;
0350     }
0351 
0352     /**
0353      * Set captcha image base URL
0354      *
0355      * @param  string $imgUrl
0356      * @return Zend_Captcha_Image
0357      */
0358     public function setImgUrl($imgUrl)
0359     {
0360         $this->_imgUrl = rtrim($imgUrl, "/\\") . '/';
0361         return $this;
0362     }
0363 
0364     /**
0365      * Set image alternative text
0366      *
0367      * @param string $imgAlt
0368      * @return Zend_Captcha_Image
0369      */
0370     public function setImgAlt ($imgAlt)
0371     {
0372         $this->_imgAlt = $imgAlt;
0373         return $this;
0374     }
0375 
0376     /**
0377      * Set captch image filename suffix
0378      *
0379      * @param  string $suffix
0380      * @return Zend_Captcha_Image
0381      */
0382     public function setSuffix($suffix)
0383     {
0384         $this->_suffix = $suffix;
0385         return $this;
0386     }
0387 
0388     /**
0389      * Set captcha image width
0390      *
0391      * @param  int $width
0392      * @return Zend_Captcha_Image
0393      */
0394     public function setWidth($width)
0395     {
0396         $this->_width = $width;
0397         return $this;
0398     }
0399 
0400     /**
0401      * Generate random frequency
0402      *
0403      * @return float
0404      */
0405     protected function _randomFreq()
0406     {
0407         return mt_rand(700000, 1000000) / 15000000;
0408     }
0409 
0410     /**
0411      * Generate random phase
0412      *
0413      * @return float
0414      */
0415     protected function _randomPhase()
0416     {
0417         // random phase from 0 to pi
0418         return mt_rand(0, 3141592) / 1000000;
0419     }
0420 
0421     /**
0422      * Generate random character size
0423      *
0424      * @return int
0425      */
0426     protected function _randomSize()
0427     {
0428         return mt_rand(300, 700) / 100;
0429     }
0430 
0431     /**
0432      * Generate captcha
0433      *
0434      * @return string captcha ID
0435      */
0436     public function generate()
0437     {
0438         $id = parent::generate();
0439         $tries = 5;
0440         // If there's already such file, try creating a new ID
0441         while($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) {
0442             $id = $this->_generateRandomId();
0443             $this->_setId($id);
0444         }
0445         $this->_generateImage($id, $this->getWord());
0446 
0447         if (mt_rand(1, $this->getGcFreq()) == 1) {
0448             $this->_gc();
0449         }
0450         return $id;
0451     }
0452 
0453     /**
0454      * Generate image captcha
0455      *
0456      * Override this function if you want different image generator
0457      * Wave transform from http://www.captcha.ru/captchas/multiwave/
0458      *
0459      * @param string $id   Captcha ID
0460      * @param string $word Captcha word
0461      * @throws Zend_Captcha_Exception
0462      */
0463     protected function _generateImage($id, $word)
0464     {
0465         if (!extension_loaded("gd")) {
0466             // require_once 'Zend/Captcha/Exception.php';
0467             throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension");
0468         }
0469 
0470         if (!function_exists("imagepng")) {
0471             // require_once 'Zend/Captcha/Exception.php';
0472             throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support");
0473         }
0474 
0475         if (!function_exists("imageftbbox")) {
0476             // require_once 'Zend/Captcha/Exception.php';
0477             throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support");
0478         }
0479 
0480         $font = $this->getFont();
0481 
0482         if (empty($font)) {
0483             // require_once 'Zend/Captcha/Exception.php';
0484             throw new Zend_Captcha_Exception("Image CAPTCHA requires font");
0485         }
0486 
0487         $w     = $this->getWidth();
0488         $h     = $this->getHeight();
0489         $fsize = $this->getFontSize();
0490 
0491         $img_file   = $this->getImgDir() . $id . $this->getSuffix();
0492         if(empty($this->_startImage)) {
0493             $img        = imagecreatetruecolor($w, $h);
0494         } else {
0495             $img = imagecreatefrompng($this->_startImage);
0496             if(!$img) {
0497                 // require_once 'Zend/Captcha/Exception.php';
0498                 throw new Zend_Captcha_Exception("Can not load start image");
0499             }
0500             $w = imagesx($img);
0501             $h = imagesy($img);
0502         }
0503         $text_color = imagecolorallocate($img, 0, 0, 0);
0504         $bg_color   = imagecolorallocate($img, 255, 255, 255);
0505         imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color);
0506         $textbox = imageftbbox($fsize, 0, $font, $word);
0507         $x = ($w - ($textbox[2] - $textbox[0])) / 2;
0508         $y = ($h - ($textbox[7] - $textbox[1])) / 2;
0509         imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word);
0510 
0511        // generate noise
0512         for ($i=0; $i<$this->_dotNoiseLevel; $i++) {
0513            imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color);
0514         }
0515         for($i=0; $i<$this->_lineNoiseLevel; $i++) {
0516            imageline($img, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color);
0517         }
0518 
0519         // transformed image
0520         $img2     = imagecreatetruecolor($w, $h);
0521         $bg_color = imagecolorallocate($img2, 255, 255, 255);
0522         imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color);
0523         // apply wave transforms
0524         $freq1 = $this->_randomFreq();
0525         $freq2 = $this->_randomFreq();
0526         $freq3 = $this->_randomFreq();
0527         $freq4 = $this->_randomFreq();
0528 
0529         $ph1 = $this->_randomPhase();
0530         $ph2 = $this->_randomPhase();
0531         $ph3 = $this->_randomPhase();
0532         $ph4 = $this->_randomPhase();
0533 
0534         $szx = $this->_randomSize();
0535         $szy = $this->_randomSize();
0536 
0537         for ($x = 0; $x < $w; $x++) {
0538             for ($y = 0; $y < $h; $y++) {
0539                 $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx;
0540                 $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy;
0541 
0542                 if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) {
0543                     continue;
0544                 } else {
0545                     $color    = (imagecolorat($img, $sx, $sy) >> 16)         & 0xFF;
0546                     $color_x  = (imagecolorat($img, $sx + 1, $sy) >> 16)     & 0xFF;
0547                     $color_y  = (imagecolorat($img, $sx, $sy + 1) >> 16)     & 0xFF;
0548                     $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF;
0549                 }
0550                 if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) {
0551                     // ignore background
0552                     continue;
0553                 } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) {
0554                     // transfer inside of the image as-is
0555                     $newcolor = 0;
0556                 } else {
0557                     // do antialiasing for border items
0558                     $frac_x  = $sx-floor($sx);
0559                     $frac_y  = $sy-floor($sy);
0560                     $frac_x1 = 1-$frac_x;
0561                     $frac_y1 = 1-$frac_y;
0562 
0563                     $newcolor = $color    * $frac_x1 * $frac_y1
0564                               + $color_x  * $frac_x  * $frac_y1
0565                               + $color_y  * $frac_x1 * $frac_y
0566                               + $color_xy * $frac_x  * $frac_y;
0567                 }
0568                 imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor));
0569             }
0570         }
0571 
0572         // generate noise
0573         for ($i=0; $i<$this->_dotNoiseLevel; $i++) {
0574             imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color);
0575         }
0576         for ($i=0; $i<$this->_lineNoiseLevel; $i++) {
0577            imageline($img2, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color);
0578         }
0579 
0580         imagepng($img2, $img_file);
0581         imagedestroy($img);
0582         imagedestroy($img2);
0583     }
0584 
0585     /**
0586      * Remove old files from image directory
0587      */
0588     protected function _gc()
0589     {
0590         $expire = time() - $this->getExpiration();
0591         $imgdir = $this->getImgDir();
0592         if(!$imgdir || strlen($imgdir) < 2) {
0593             // safety guard
0594             return;
0595         }
0596         $suffixLength = strlen($this->_suffix);
0597         foreach (new DirectoryIterator($imgdir) as $file) {
0598             if (!$file->isDot() && !$file->isDir()) {
0599                 if (file_exists($file->getPathname()) && $file->getMTime() < $expire) {
0600                     // only deletes files ending with $this->_suffix
0601                     if (substr($file->getFilename(), -($suffixLength)) == $this->_suffix) {
0602                         unlink($file->getPathname());
0603                     }
0604                 }
0605             }
0606         }
0607     }
0608 
0609     /**
0610      * Display the captcha
0611      *
0612      * @param Zend_View_Interface $view
0613      * @param mixed $element
0614      * @return string
0615      */
0616     public function render(Zend_View_Interface $view = null, $element = null)
0617     {
0618         $endTag = ' />';
0619         if (($view instanceof Zend_View_Abstract) && !$view->doctype()->isXhtml()) {
0620             $endTag = '>';
0621         }
0622         return '<img width="' . $this->getWidth() . '" height="' . $this->getHeight() . '" alt="' . $this->getImgAlt()
0623              . '" src="' . $this->getImgUrl() . $this->getId() . $this->getSuffix() . '"' . $endTag;
0624     }
0625 }