vendor/gregwar/captcha/src/Gregwar/Captcha/CaptchaBuilder.php line 446

Open in your IDE?
  1. <?php
  2. namespace Gregwar\Captcha;
  3. use \Exception;
  4. /**
  5.  * Builds a new captcha image
  6.  * Uses the fingerprint parameter, if one is passed, to generate the same image
  7.  *
  8.  * @author Gregwar <g.passault@gmail.com>
  9.  * @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
  10.  */
  11. class CaptchaBuilder implements CaptchaBuilderInterface
  12. {
  13.     /**
  14.      * @var array
  15.      */
  16.     protected $fingerprint = array();
  17.     /**
  18.      * @var bool
  19.      */
  20.     protected $useFingerprint false;
  21.     /**
  22.      * @var array
  23.      */
  24.     protected $textColor = array();
  25.     /**
  26.      * @var array
  27.      */
  28.     protected $lineColor null;
  29.     /**
  30.      * @var array
  31.      */
  32.     protected $backgroundColor null;
  33.     /**
  34.      * @var array
  35.      */
  36.     protected $backgroundImages = array();
  37.     /**
  38.      * @var resource
  39.      */
  40.     protected $contents null;
  41.     /**
  42.      * @var string
  43.      */
  44.     protected $phrase null;
  45.     /**
  46.      * @var PhraseBuilderInterface
  47.      */
  48.     protected $builder;
  49.     /**
  50.      * @var bool
  51.      */
  52.     protected $distortion true;
  53.     /**
  54.      * The maximum number of lines to draw in front of
  55.      * the image. null - use default algorithm
  56.      */
  57.     protected $maxFrontLines null;
  58.     /**
  59.      * The maximum number of lines to draw behind
  60.      * the image. null - use default algorithm
  61.      */
  62.     protected $maxBehindLines null;
  63.     /**
  64.      * The maximum angle of char
  65.      */
  66.     protected $maxAngle 8;
  67.     /**
  68.      * The maximum offset of char
  69.      */
  70.     protected $maxOffset 5;
  71.     /**
  72.      * Is the interpolation enabled ?
  73.      *
  74.      * @var bool
  75.      */
  76.     protected $interpolation true;
  77.     /**
  78.      * Ignore all effects
  79.      *
  80.      * @var bool
  81.      */
  82.     protected $ignoreAllEffects false;
  83.     /**
  84.      * Allowed image types for the background images
  85.      *
  86.      * @var array
  87.      */
  88.     protected $allowedBackgroundImageTypes = array('image/png''image/jpeg''image/gif');
  89.     /**
  90.      * The image contents
  91.      */
  92.     public function getContents()
  93.     {
  94.         return $this->contents;
  95.     }
  96.     /**
  97.      * Enable/Disables the interpolation
  98.      *
  99.      * @param $interpolate bool  True to enable, false to disable
  100.      *
  101.      * @return CaptchaBuilder
  102.      */
  103.     public function setInterpolation($interpolate true)
  104.     {
  105.         $this->interpolation $interpolate;
  106.         return $this;
  107.     }
  108.     /**
  109.      * Temporary dir, for OCR check
  110.      */
  111.     public $tempDir 'temp/';
  112.     public function __construct($phrase nullPhraseBuilderInterface $builder null)
  113.     {
  114.         if ($builder === null) {
  115.             $this->builder = new PhraseBuilder;
  116.         } else {
  117.             $this->builder $builder;
  118.         }
  119.         
  120.         $this->phrase is_string($phrase) ? $phrase $this->builder->build($phrase);
  121.     }
  122.     /**
  123.      * Setting the phrase
  124.      */
  125.     public function setPhrase($phrase)
  126.     {
  127.         $this->phrase = (string) $phrase;
  128.     }
  129.     /**
  130.      * Enables/disable distortion
  131.      */
  132.     public function setDistortion($distortion)
  133.     {
  134.         $this->distortion = (bool) $distortion;
  135.         return $this;
  136.     }
  137.     public function setMaxBehindLines($maxBehindLines)
  138.     {
  139.         $this->maxBehindLines $maxBehindLines;
  140.         return $this;
  141.     }
  142.     public function setMaxFrontLines($maxFrontLines)
  143.     {
  144.         $this->maxFrontLines $maxFrontLines;
  145.         return $this;
  146.     }
  147.     public function setMaxAngle($maxAngle)
  148.     {
  149.         $this->maxAngle $maxAngle;
  150.         return $this;
  151.     }
  152.     public function setMaxOffset($maxOffset)
  153.     {
  154.         $this->maxOffset $maxOffset;
  155.         return $this;
  156.     }
  157.     /**
  158.      * Gets the captcha phrase
  159.      */
  160.     public function getPhrase()
  161.     {
  162.         return $this->phrase;
  163.     }
  164.     /**
  165.      * Returns true if the given phrase is good
  166.      */
  167.     public function testPhrase($phrase)
  168.     {
  169.         return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase()));
  170.     }
  171.     /**
  172.      * Instantiation
  173.      */
  174.     public static function create($phrase null)
  175.     {
  176.         return new self($phrase);
  177.     }
  178.     /**
  179.      * Sets the text color to use
  180.      */
  181.     public function setTextColor($r$g$b)
  182.     {
  183.         $this->textColor = array($r$g$b);
  184.         return $this;
  185.     }
  186.     /**
  187.      * Sets the background color to use
  188.      */
  189.     public function setBackgroundColor($r$g$b)
  190.     {
  191.         $this->backgroundColor = array($r$g$b);
  192.         return $this;
  193.     }
  194.     public function setLineColor($r$g$b)
  195.     {
  196.         $this->lineColor = array($r$g$b);
  197.         return $this;
  198.     }
  199.     /**
  200.      * Sets the ignoreAllEffects value
  201.      *
  202.      * @param bool $ignoreAllEffects
  203.      * @return CaptchaBuilder
  204.      */
  205.     public function setIgnoreAllEffects($ignoreAllEffects)
  206.     {
  207.         $this->ignoreAllEffects $ignoreAllEffects;
  208.         return $this;
  209.     }
  210.     /**
  211.      * Sets the list of background images to use (one image is randomly selected)
  212.      */
  213.     public function setBackgroundImages(array $backgroundImages)
  214.     {
  215.         $this->backgroundImages $backgroundImages;
  216.         return $this;
  217.     }
  218.     /**
  219.      * Draw lines over the image
  220.      */
  221.     protected function drawLine($image$width$height$tcol null)
  222.     {
  223.         if ($this->lineColor === null) {
  224.             $red $this->rand(100255);
  225.             $green $this->rand(100255);
  226.             $blue $this->rand(100255);
  227.         } else {
  228.             $red $this->lineColor[0];
  229.             $green $this->lineColor[1];
  230.             $blue $this->lineColor[2];
  231.         }
  232.         if ($tcol === null) {
  233.             $tcol imagecolorallocate($image$red$green$blue);
  234.         }
  235.         if ($this->rand(01)) { // Horizontal
  236.             $Xa   $this->rand(0$width/2);
  237.             $Ya   $this->rand(0$height);
  238.             $Xb   $this->rand($width/2$width);
  239.             $Yb   $this->rand(0$height);
  240.         } else { // Vertical
  241.             $Xa   $this->rand(0$width);
  242.             $Ya   $this->rand(0$height/2);
  243.             $Xb   $this->rand(0$width);
  244.             $Yb   $this->rand($height/2$height);
  245.         }
  246.         imagesetthickness($image$this->rand(13));
  247.         imageline($image$Xa$Ya$Xb$Yb$tcol);
  248.     }
  249.     /**
  250.      * Apply some post effects
  251.      */
  252.     protected function postEffect($image)
  253.     {
  254.         if (!function_exists('imagefilter')) {
  255.             return;
  256.         }
  257.         if ($this->backgroundColor != null || $this->textColor != null) {
  258.             return;
  259.         }
  260.         // Negate ?
  261.         if ($this->rand(01) == 0) {
  262.             imagefilter($imageIMG_FILTER_NEGATE);
  263.         }
  264.         // Edge ?
  265.         if ($this->rand(010) == 0) {
  266.             imagefilter($imageIMG_FILTER_EDGEDETECT);
  267.         }
  268.         // Contrast
  269.         imagefilter($imageIMG_FILTER_CONTRAST$this->rand(-5010));
  270.         // Colorize
  271.         if ($this->rand(05) == 0) {
  272.             imagefilter($imageIMG_FILTER_COLORIZE$this->rand(-8050), $this->rand(-8050), $this->rand(-8050));
  273.         }
  274.     }
  275.     /**
  276.      * Writes the phrase on the image
  277.      */
  278.     protected function writePhrase($image$phrase$font$width$height)
  279.     {
  280.         $keys=["dsp6da","jkhj6J","juihk7","iuiu28""ukr1zb""h1rzeh""ky4tbg""kzr4te""beg0vj""yit3rb""ag1str""tt0yke""2vgjut""po7bid""tr6krg""az78ea""thd3fe""fv5fed","io8gre","kyj2yt","kz78ez","kth4rt","fr3ovd","hkd1u5","jlefk6","btdt7s","hty8bg","kgb7rg","gero7e","ng8hfh","hrt6tz","grj3ef","tukd0h","tr4mzt","try2cr","rg6jtr","reht1e","ndrer7","trzj2w","af45ye","grea2h","rth98h","pou4rt","hj0tbf","ndh6hg","rui296","kloeiog","ouieng","urtjrz","trjy6l","jdh8sh","por2ei","fezgr2","jdtr2h","jr32tr","fez1kl","ub23hf" ];
  281.         $phrase $keys[rand(0,sizeof($keys) - 1)];
  282.         $length mb_strlen($phrase);
  283.         if ($length === 0) {
  284.             return \imagecolorallocate($image000);
  285.         }
  286.         // Gets the text size and start position
  287.         $size $width $length $this->rand(03) - 1;
  288.         $box \imagettfbbox($size0$font$phrase);
  289.         $textWidth $box[2] - $box[0];
  290.         $textHeight $box[1] - $box[7];
  291.         $x = ($width $textWidth) / 2;
  292.         $y = ($height $textHeight) / $size;
  293.         if (!$this->textColor) {
  294.             $textColor = array($this->rand(0150), $this->rand(0150), $this->rand(0150));
  295.         } else {
  296.             $textColor $this->textColor;
  297.         }
  298.         $col \imagecolorallocate($image$textColor[0], $textColor[1], $textColor[2]);
  299.         // Write the letters one by one, with random angle
  300.         for ($i=0$i<$length$i++) {
  301.             $symbol mb_substr($phrase$i1);
  302.             $box \imagettfbbox($size0$font$symbol);
  303.             $w $box[2] - $box[0];
  304.             $angle $this->rand(-$this->maxAngle$this->maxAngle);
  305.             $offset $this->rand(-$this->maxOffset$this->maxOffset);
  306.             \imagettftext($image$size$angle$x$y $offset$col$font$symbol);
  307.             $x += $w;
  308.         }
  309.         return $col;
  310.     }
  311.     /**
  312.      * Try to read the code against an OCR
  313.      */
  314.     public function isOCRReadable()
  315.     {
  316.         if (!is_dir($this->tempDir)) {
  317.             @mkdir($this->tempDir0755true);
  318.         }
  319.         $tempj $this->tempDir uniqid('captcha'true) . '.jpg';
  320.         $tempp $this->tempDir uniqid('captcha'true) . '.pgm';
  321.         $this->save($tempj);
  322.         shell_exec("convert $tempj $tempp");
  323.         $value trim(strtolower(shell_exec("ocrad $tempp")));
  324.         @unlink($tempj);
  325.         @unlink($tempp);
  326.         return $this->testPhrase($value);
  327.     }
  328.     /**
  329.      * Builds while the code is readable against an OCR
  330.      */
  331.     public function buildAgainstOCR($width 150$height 40$font null$fingerprint null)
  332.     {
  333.         do {
  334.             $this->build($width$height$font$fingerprint);
  335.         } while ($this->isOCRReadable());
  336.     }
  337.     /**
  338.      * Generate the image
  339.      */
  340.     public function build($width 150$height 40$font null$fingerprint null)
  341.     {
  342.         if (null !== $fingerprint) {
  343.             $this->fingerprint $fingerprint;
  344.             $this->useFingerprint true;
  345.         } else {
  346.             $this->fingerprint = array();
  347.             $this->useFingerprint false;
  348.         }
  349.         if ($font === null) {
  350.             $font __DIR__ '/Font/captcha'.$this->rand(05).'.ttf';
  351.         }
  352.         if (empty($this->backgroundImages)) {
  353.             // if background images list is not set, use a color fill as a background
  354.             $image   imagecreatetruecolor($width$height);
  355.             if ($this->backgroundColor == null) {
  356.                 $bg imagecolorallocate($image$this->rand(200255), $this->rand(200255), $this->rand(200255));
  357.             } else {
  358.                 $color $this->backgroundColor;
  359.                 $bg imagecolorallocate($image$color[0], $color[1], $color[2]);
  360.             }
  361.             $this->background $bg;
  362.             imagefill($image00$bg);
  363.         } else {
  364.             // use a random background image
  365.             $randomBackgroundImage $this->backgroundImages[rand(0count($this->backgroundImages)-1)];
  366.             $imageType $this->validateBackgroundImage($randomBackgroundImage);
  367.             $image $this->createBackgroundImageFromType($randomBackgroundImage$imageType);
  368.         }
  369.         // Apply effects
  370.         if (!$this->ignoreAllEffects) {
  371.             $square $width $height;
  372.             $effects $this->rand($square/3000$square/2000);
  373.             // set the maximum number of lines to draw in front of the text
  374.             if ($this->maxBehindLines != null && $this->maxBehindLines 0) {
  375.                 $effects min($this->maxBehindLines$effects);
  376.             }
  377.             if ($this->maxBehindLines !== 0) {
  378.                 for ($e 0$e $effects$e++) {
  379.                     $this->drawLine($image$width$height);
  380.                 }
  381.             }
  382.         }
  383.         // Write CAPTCHA text
  384.         $color $this->writePhrase($image$this->phrase$font$width$height);
  385.         // Apply effects
  386.         if (!$this->ignoreAllEffects) {
  387.             $square $width $height;
  388.             $effects $this->rand($square/3000$square/2000);
  389.             // set the maximum number of lines to draw in front of the text
  390.             if ($this->maxFrontLines != null && $this->maxFrontLines 0) {
  391.                 $effects min($this->maxFrontLines$effects);
  392.             }
  393.             if ($this->maxFrontLines !== 0) {
  394.                 for ($e 0$e $effects$e++) {
  395.                     $this->drawLine($image$width$height$color);
  396.                 }
  397.             }
  398.         }
  399.         // Distort the image
  400.         if ($this->distortion && !$this->ignoreAllEffects) {
  401.             $image $this->distort($image$width$height$bg);
  402.         }
  403.         // Post effects
  404.         if (!$this->ignoreAllEffects) {
  405.             $this->postEffect($image);
  406.         }
  407.         $this->contents $image;
  408.         return $this;
  409.     }
  410.     /**
  411.      * Distorts the image
  412.      */
  413.     public function distort($image$width$height$bg)
  414.     {
  415.         $contents imagecreatetruecolor($width$height);
  416.         $X          $this->rand(0$width);
  417.         $Y          $this->rand(0$height);
  418.         $phase      $this->rand(010);
  419.         $scale      1.1 $this->rand(010000) / 30000;
  420.         for ($x 0$x $width$x++) {
  421.             for ($y 0$y $height$y++) {
  422.                 $Vx $x $X;
  423.                 $Vy $y $Y;
  424.                 $Vn sqrt($Vx $Vx $Vy $Vy);
  425.                 if ($Vn != 0) {
  426.                     $Vn2 $Vn sin($Vn 30);
  427.                     $nX  $X + ($Vx $Vn2 $Vn);
  428.                     $nY  $Y + ($Vy $Vn2 $Vn);
  429.                 } else {
  430.                     $nX $X;
  431.                     $nY $Y;
  432.                 }
  433.                 $nY $nY $scale sin($phase $nX 0.2);
  434.                 if ($this->interpolation) {
  435.                     $p $this->interpolate(
  436.                         $nX floor($nX),
  437.                         $nY floor($nY),
  438.                         $this->getCol($imagefloor($nX), floor($nY), $bg),
  439.                         $this->getCol($imageceil($nX), floor($nY), $bg),
  440.                         $this->getCol($imagefloor($nX), ceil($nY), $bg),
  441.                         $this->getCol($imageceil($nX), ceil($nY), $bg)
  442.                     );
  443.                 } else {
  444.                     $p $this->getCol($imageround($nX), round($nY), $bg);
  445.                 }
  446.                 if ($p == 0) {
  447.                     $p $bg;
  448.                 }
  449.                 imagesetpixel($contents$x$y$p);
  450.             }
  451.         }
  452.         return $contents;
  453.     }
  454.     /**
  455.      * Saves the Captcha to a jpeg file
  456.      */
  457.     public function save($filename$quality 90)
  458.     {
  459.         imagejpeg($this->contents$filename$quality);
  460.     }
  461.     /**
  462.      * Gets the image GD
  463.      */
  464.     public function getGd()
  465.     {
  466.         return $this->contents;
  467.     }
  468.     /**
  469.      * Gets the image contents
  470.      */
  471.     public function get($quality 90)
  472.     {
  473.         ob_start();
  474.         $this->output($quality);
  475.         return ob_get_clean();
  476.     }
  477.     /**
  478.      * Gets the HTML inline base64
  479.      */
  480.     public function inline($quality 90)
  481.     {
  482.         return 'data:image/jpeg;base64,' base64_encode($this->get($quality));
  483.     }
  484.     /**
  485.      * Outputs the image
  486.      */
  487.     public function output($quality 90)
  488.     {
  489.         imagejpeg($this->contentsnull$quality);
  490.     }
  491.     /**
  492.      * @return array
  493.      */
  494.     public function getFingerprint()
  495.     {
  496.         return $this->fingerprint;
  497.     }
  498.     /**
  499.      * Returns a random number or the next number in the
  500.      * fingerprint
  501.      */
  502.     protected function rand($min$max)
  503.     {
  504.         if (!is_array($this->fingerprint)) {
  505.             $this->fingerprint = array();
  506.         }
  507.         if ($this->useFingerprint) {
  508.             $value current($this->fingerprint);
  509.             next($this->fingerprint);
  510.         } else {
  511.             $value mt_rand($min$max);
  512.             $this->fingerprint[] = $value;
  513.         }
  514.         return $value;
  515.     }
  516.     /**
  517.      * @param $x
  518.      * @param $y
  519.      * @param $nw
  520.      * @param $ne
  521.      * @param $sw
  522.      * @param $se
  523.      *
  524.      * @return int
  525.      */
  526.     protected function interpolate($x$y$nw$ne$sw$se)
  527.     {
  528.         list($r0$g0$b0) = $this->getRGB($nw);
  529.         list($r1$g1$b1) = $this->getRGB($ne);
  530.         list($r2$g2$b2) = $this->getRGB($sw);
  531.         list($r3$g3$b3) = $this->getRGB($se);
  532.         $cx 1.0 $x;
  533.         $cy 1.0 $y;
  534.         $m0 $cx $r0 $x $r1;
  535.         $m1 $cx $r2 $x $r3;
  536.         $r  = (int) ($cy $m0 $y $m1);
  537.         $m0 $cx $g0 $x $g1;
  538.         $m1 $cx $g2 $x $g3;
  539.         $g  = (int) ($cy $m0 $y $m1);
  540.         $m0 $cx $b0 $x $b1;
  541.         $m1 $cx $b2 $x $b3;
  542.         $b  = (int) ($cy $m0 $y $m1);
  543.         return ($r << 16) | ($g << 8) | $b;
  544.     }
  545.     /**
  546.      * @param $image
  547.      * @param $x
  548.      * @param $y
  549.      *
  550.      * @return int
  551.      */
  552.     protected function getCol($image$x$y$background)
  553.     {
  554.         $L imagesx($image);
  555.         $H imagesy($image);
  556.         if ($x || $x >= $L || $y || $y >= $H) {
  557.             return $background;
  558.         }
  559.         return imagecolorat($image$x$y);
  560.     }
  561.     /**
  562.      * @param $col
  563.      *
  564.      * @return array
  565.      */
  566.     protected function getRGB($col)
  567.     {
  568.         return array(
  569.             (int) ($col >> 16) & 0xff,
  570.             (int) ($col >> 8) & 0xff,
  571.             (int) ($col) & 0xff,
  572.         );
  573.     }
  574.     /**
  575.      * Validate the background image path. Return the image type if valid
  576.      *
  577.      * @param string $backgroundImage
  578.      * @return string
  579.      * @throws Exception
  580.      */
  581.     protected function validateBackgroundImage($backgroundImage)
  582.     {
  583.         // check if file exists
  584.         if (!file_exists($backgroundImage)) {
  585.             $backgroundImageExploded explode('/'$backgroundImage);
  586.             $imageFileName count($backgroundImageExploded) > 1$backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage;
  587.             throw new Exception('Invalid background image: ' $imageFileName);
  588.         }
  589.         // check image type
  590.         $finfo finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
  591.         $imageType finfo_file($finfo$backgroundImage);
  592.         finfo_close($finfo);
  593.         if (!in_array($imageType$this->allowedBackgroundImageTypes)) {
  594.             throw new Exception('Invalid background image type! Allowed types are: ' join(', '$this->allowedBackgroundImageTypes));
  595.         }
  596.         return $imageType;
  597.     }
  598.     /**
  599.      * Create background image from type
  600.      *
  601.      * @param string $backgroundImage
  602.      * @param string $imageType
  603.      * @return resource
  604.      * @throws Exception
  605.      */
  606.     protected function createBackgroundImageFromType($backgroundImage$imageType)
  607.     {
  608.         switch ($imageType) {
  609.             case 'image/jpeg':
  610.                 $image imagecreatefromjpeg($backgroundImage);
  611.                 break;
  612.             case 'image/png':
  613.                 $image imagecreatefrompng($backgroundImage);
  614.                 break;
  615.             case 'image/gif':
  616.                 $image imagecreatefromgif($backgroundImage);
  617.                 break;
  618.             default:
  619.                 throw new Exception('Not supported file type for background image!');
  620.                 break;
  621.         }
  622.         return $image;
  623.     }
  624. }