gluon-web-remote

Web remote Administration for big size of gluon routers
git clone git://archive.git.mtrnord.blog/MTRNord/gluon-web-remote.git
Log | Files | Refs | README

CaptchaBuilder.php (18598B)


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