easyappointments/application/libraries/Captcha_builder.php

742 lines
19 KiB
PHP
Raw Normal View History

2022-02-23 15:42:05 +03:00
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.3
* ---------------------------------------------------------------------------- */
use Gregwar\Captcha\PhraseBuilder;
use Gregwar\Captcha\PhraseBuilderInterface;
/**
* Class Captcha_builder
*
* This class replaces the Gregwar\Captcha\CaptchaBuilder so that it becomes PHP 8.1 compatible.
*/
class Captcha_builder
{
/**
* Temporary dir, for OCR check
*/
public $tempDir = 'temp/';
2022-02-23 15:42:05 +03:00
/**
* @var array
*/
protected $fingerprint = [];
/**
* @var bool
*/
protected $useFingerprint = false;
2022-02-23 15:42:05 +03:00
/**
* @var array
*/
protected $textColor = [];
/**
* @var array
*/
protected $lineColor = null;
2022-02-23 15:42:05 +03:00
/**
* @var array
*/
protected $background = null;
/**
* @var array
2022-02-23 15:42:05 +03:00
*/
protected $backgroundColor = null;
2022-02-23 15:42:05 +03:00
/**
* @var array
*/
protected $backgroundImages = [];
/**
* @var resource
*/
protected $contents = null;
2022-02-23 15:42:05 +03:00
/**
* @var string
*/
protected $phrase = null;
2022-02-23 15:42:05 +03:00
/**
* @var PhraseBuilderInterface
*/
protected $builder;
/**
* @var bool
*/
protected $distortion = true;
2022-02-23 15:42:05 +03:00
/**
* The maximum number of lines to draw in front of
* the image. null - use default algorithm
*/
protected $maxFrontLines = null;
2022-02-23 15:42:05 +03:00
/**
* The maximum number of lines to draw behind
* the image. null - use default algorithm
*/
protected $maxBehindLines = null;
2022-02-23 15:42:05 +03:00
/**
* The maximum angle of char
*/
protected $maxAngle = 8;
/**
* The maximum offset of char
*/
protected $maxOffset = 5;
/**
* Is the interpolation enabled ?
*
* @var bool
*/
protected $interpolation = true;
2022-02-23 15:42:05 +03:00
/**
* Ignore all effects
*
* @var bool
*/
protected $ignoreAllEffects = false;
2022-02-23 15:42:05 +03:00
/**
* Allowed image types for the background images
*
* @var array
*/
protected $allowedBackgroundImageTypes = ['image/png', 'image/jpeg', 'image/gif'];
2024-12-19 21:13:51 +03:00
public function __construct($phrase = null, ?PhraseBuilderInterface $builder = null)
2022-02-23 15:42:05 +03:00
{
if ($builder === null) {
$this->builder = new PhraseBuilder();
} else {
2022-02-23 15:42:05 +03:00
$this->builder = $builder;
}
$this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase);
}
/**
* Generate the image
2024-12-19 21:13:51 +03:00
* @throws Exception
2022-02-23 15:42:05 +03:00
*/
public function build($width = 150, $height = 40, $font = null, $fingerprint = null)
2022-02-23 15:42:05 +03:00
{
if (null !== $fingerprint) {
$this->fingerprint = $fingerprint;
$this->useFingerprint = true;
} else {
$this->fingerprint = [];
$this->useFingerprint = false;
}
2022-02-23 15:42:05 +03:00
if ($font === null) {
$font =
__DIR__ . '/../../vendor/gregwar/captcha/src/Gregwar/Captcha/Font/captcha' . $this->rand(0, 5) . '.ttf';
}
2022-02-23 15:42:05 +03:00
2024-12-19 21:13:51 +03:00
$bg = null;
if (empty($this->backgroundImages)) {
// if background images list is not set, use a color fill as a background
$image = imagecreatetruecolor($width, $height);
if ($this->backgroundColor == null) {
$bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255));
} else {
$color = $this->backgroundColor;
$bg = imagecolorallocate($image, $color[0], $color[1], $color[2]);
}
$this->background = $bg;
imagefill($image, 0, 0, $bg);
} else {
// use a random background image
$randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages) - 1)];
2022-02-23 15:42:05 +03:00
$imageType = $this->validateBackgroundImage($randomBackgroundImage);
2022-02-23 15:42:05 +03:00
$image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType);
}
2022-02-23 15:42:05 +03:00
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square / 3000, $square / 2000);
2022-02-23 15:42:05 +03:00
// set the maximum number of lines to draw in front of the text
if ($this->maxBehindLines != null && $this->maxBehindLines > 0) {
$effects = min($this->maxBehindLines, $effects);
}
2022-02-23 15:42:05 +03:00
if ($this->maxBehindLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height);
}
}
}
2022-02-23 15:42:05 +03:00
// Write CAPTCHA text
$color = $this->writePhrase($image, $this->phrase, $font, $width, $height);
2022-02-23 15:42:05 +03:00
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square / 3000, $square / 2000);
2022-02-23 15:42:05 +03:00
// set the maximum number of lines to draw in front of the text
if ($this->maxFrontLines != null && $this->maxFrontLines > 0) {
$effects = min($this->maxFrontLines, $effects);
}
2022-02-23 15:42:05 +03:00
if ($this->maxFrontLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height, $color);
}
}
}
2022-02-23 15:42:05 +03:00
// Distort the image
if ($this->distortion && !$this->ignoreAllEffects) {
$image = $this->distort($image, $width, $height, $bg);
}
2022-02-23 15:42:05 +03:00
// Post effects
if (!$this->ignoreAllEffects) {
$this->postEffect($image);
}
2022-02-23 15:42:05 +03:00
$this->contents = $image;
2022-02-23 15:42:05 +03:00
return $this;
}
/**
* Returns a random number or the next number in the
* fingerprint
2022-02-23 15:42:05 +03:00
*/
protected function rand($min, $max)
2022-02-23 15:42:05 +03:00
{
if (!is_array($this->fingerprint)) {
$this->fingerprint = [];
}
2022-02-23 15:42:05 +03:00
if ($this->useFingerprint) {
$value = current($this->fingerprint);
next($this->fingerprint);
} else {
$value = mt_rand((int) $min, (int) $max);
$this->fingerprint[] = $value;
}
2022-02-23 15:42:05 +03:00
return $value;
2022-02-23 15:42:05 +03:00
}
/**
* Validate the background image path. Return the image type if valid
2022-02-23 15:42:05 +03:00
*
* @param string $backgroundImage
* @return string
* @throws Exception
2022-02-23 15:42:05 +03:00
*/
protected function validateBackgroundImage($backgroundImage)
2022-02-23 15:42:05 +03:00
{
// check if file exists
if (!file_exists($backgroundImage)) {
$backgroundImageExploded = explode('/', $backgroundImage);
$imageFileName =
count($backgroundImageExploded) > 1
? $backgroundImageExploded[count($backgroundImageExploded) - 1]
: $backgroundImage;
2022-02-23 15:42:05 +03:00
throw new Exception('Invalid background image: ' . $imageFileName);
}
// check image type
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$imageType = finfo_file($finfo, $backgroundImage);
finfo_close($finfo);
if (!in_array($imageType, $this->allowedBackgroundImageTypes)) {
throw new Exception(
'Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes),
);
}
return $imageType;
2022-02-23 15:42:05 +03:00
}
/**
* Create background image from type
*
* @param string $backgroundImage
* @param string $imageType
* @return resource
* @throws Exception
2022-02-23 15:42:05 +03:00
*/
protected function createBackgroundImageFromType($backgroundImage, $imageType)
2022-02-23 15:42:05 +03:00
{
switch ($imageType) {
case 'image/jpeg':
$image = imagecreatefromjpeg($backgroundImage);
break;
case 'image/png':
$image = imagecreatefrompng($backgroundImage);
break;
case 'image/gif':
$image = imagecreatefromgif($backgroundImage);
break;
2022-02-23 15:42:05 +03:00
default:
throw new Exception('Not supported file type for background image!');
break;
}
return $image;
2022-02-23 15:42:05 +03:00
}
/**
* Draw lines over the image
*/
protected function drawLine($image, $width, $height, $tcol = null)
2022-02-23 15:42:05 +03:00
{
if ($this->lineColor === null) {
2022-02-23 15:42:05 +03:00
$red = $this->rand(100, 255);
$green = $this->rand(100, 255);
$blue = $this->rand(100, 255);
} else {
2022-02-23 15:42:05 +03:00
$red = $this->lineColor[0];
$green = $this->lineColor[1];
$blue = $this->lineColor[2];
}
if ($tcol === null) {
2022-02-23 15:42:05 +03:00
$tcol = imagecolorallocate($image, $red, $green, $blue);
}
if ($this->rand(0, 1)) {
// Horizontal
2022-02-23 15:42:05 +03:00
$Xa = $this->rand(0, $width / 2);
$Ya = $this->rand(0, $height);
$Xb = $this->rand($width / 2, $width);
$Yb = $this->rand(0, $height);
} else {
// Vertical
2022-02-23 15:42:05 +03:00
$Xa = $this->rand(0, $width);
$Ya = $this->rand(0, $height / 2);
$Xb = $this->rand(0, $width);
$Yb = $this->rand($height / 2, $height);
}
imagesetthickness($image, $this->rand(1, 3));
imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol);
}
/**
* Writes the phrase on the image
*/
protected function writePhrase($image, $phrase, $font, $width, $height)
{
$length = mb_strlen($phrase);
if ($length === 0) {
return imagecolorallocate($image, 0, 0, 0);
2022-02-23 15:42:05 +03:00
}
// Gets the text size and start position
$size = (int) round($width / $length) - $this->rand(0, 3) - 1;
$box = imagettfbbox($size, 0, $font, $phrase);
2022-02-23 15:42:05 +03:00
$textWidth = $box[2] - $box[0];
$textHeight = $box[1] - $box[7];
$x = (int) round(($width - $textWidth) / 2);
$y = (int) round(($height - $textHeight) / 2) + $size;
2022-02-23 15:42:05 +03:00
if (!$this->textColor) {
2022-02-23 15:42:05 +03:00
$textColor = [$this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150)];
} else {
2022-02-23 15:42:05 +03:00
$textColor = $this->textColor;
}
$col = imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
2022-02-23 15:42:05 +03:00
// Write the letters one by one, with random angle
for ($i = 0; $i < $length; $i++) {
2022-02-23 15:42:05 +03:00
$symbol = mb_substr($phrase, $i, 1);
$box = imagettfbbox($size, 0, $font, $symbol);
2022-02-23 15:42:05 +03:00
$w = $box[2] - $box[0];
$angle = $this->rand(-$this->maxAngle, $this->maxAngle);
$offset = $this->rand(-$this->maxOffset, $this->maxOffset);
imagettftext($image, $size, $angle, $x, $y + $offset, $col, $font, $symbol);
2022-02-23 15:42:05 +03:00
$x += $w;
}
return $col;
}
/**
* Distorts the image
2022-02-23 15:42:05 +03:00
*/
public function distort($image, $width, $height, $bg)
2022-02-23 15:42:05 +03:00
{
$contents = imagecreatetruecolor($width, $height);
$X = $this->rand(0, $width);
$Y = $this->rand(0, $height);
$phase = $this->rand(0, 10);
$scale = 1.1 + $this->rand(0, 10000) / 30000;
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
2022-02-23 15:42:05 +03:00
$Vx = $x - $X;
$Vy = $y - $Y;
$Vn = sqrt($Vx * $Vx + $Vy * $Vy);
if ($Vn != 0) {
2022-02-23 15:42:05 +03:00
$Vn2 = $Vn + 4 * sin($Vn / 30);
$nX = $X + ($Vx * $Vn2) / $Vn;
$nY = $Y + ($Vy * $Vn2) / $Vn;
} else {
2022-02-23 15:42:05 +03:00
$nX = $X;
$nY = $Y;
}
$nY = $nY + $scale * sin($phase + $nX * 0.2);
if ($this->interpolation) {
2022-02-23 15:42:05 +03:00
$p = $this->interpolate(
$nX - floor($nX),
$nY - floor($nY),
$this->getCol($image, floor($nX), floor($nY), $bg),
$this->getCol($image, ceil($nX), floor($nY), $bg),
$this->getCol($image, floor($nX), ceil($nY), $bg),
$this->getCol($image, ceil($nX), ceil($nY), $bg),
2022-02-23 15:42:05 +03:00
);
} else {
2022-02-23 15:42:05 +03:00
$p = $this->getCol($image, round($nX), round($nY), $bg);
}
if ($p == 0) {
2022-02-23 15:42:05 +03:00
$p = $bg;
}
imagesetpixel($contents, $x, $y, $p);
}
}
return $contents;
}
/**
* @param $x
* @param $y
* @param $nw
* @param $ne
* @param $sw
* @param $se
*
* @return int
2022-02-23 15:42:05 +03:00
*/
protected function interpolate($x, $y, $nw, $ne, $sw, $se)
2022-02-23 15:42:05 +03:00
{
[$r0, $g0, $b0] = $this->getRGB($nw);
[$r1, $g1, $b1] = $this->getRGB($ne);
[$r2, $g2, $b2] = $this->getRGB($sw);
[$r3, $g3, $b3] = $this->getRGB($se);
$cx = 1.0 - $x;
$cy = 1.0 - $y;
$m0 = $cx * $r0 + $x * $r1;
$m1 = $cx * $r2 + $x * $r3;
$r = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $g0 + $x * $g1;
$m1 = $cx * $g2 + $x * $g3;
$g = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $b0 + $x * $b1;
$m1 = $cx * $b2 + $x * $b3;
$b = (int) ($cy * $m0 + $y * $m1);
return ($r << 16) | ($g << 8) | $b;
2022-02-23 15:42:05 +03:00
}
/**
* @param $col
*
* @return array
2022-02-23 15:42:05 +03:00
*/
protected function getRGB($col)
2022-02-23 15:42:05 +03:00
{
return [(int) ($col >> 16) & 0xff, (int) ($col >> 8) & 0xff, (int) $col & 0xff];
2022-02-23 15:42:05 +03:00
}
/**
* @param $image
* @param $x
* @param $y
*
* @return int
2022-02-23 15:42:05 +03:00
*/
protected function getCol($image, $x, $y, $background)
2022-02-23 15:42:05 +03:00
{
$L = imagesx($image);
$H = imagesy($image);
if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
return $background;
}
2022-02-23 15:42:05 +03:00
return imagecolorat($image, $x, $y);
2022-02-23 15:42:05 +03:00
}
/**
* Apply some post effects
2022-02-23 15:42:05 +03:00
*/
protected function postEffect($image)
2022-02-23 15:42:05 +03:00
{
if (!function_exists('imagefilter')) {
return;
}
if ($this->backgroundColor != null || $this->textColor != null) {
return;
}
// Negate ?
if ($this->rand(0, 1) == 0) {
imagefilter($image, IMG_FILTER_NEGATE);
}
// Edge ?
if ($this->rand(0, 10) == 0) {
imagefilter($image, IMG_FILTER_EDGEDETECT);
}
// Contrast
imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10));
// Colorize
if ($this->rand(0, 5) == 0) {
imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50));
}
2022-02-23 15:42:05 +03:00
}
/**
* Instantiation
2022-02-23 15:42:05 +03:00
*/
public static function create($phrase = null)
2022-02-23 15:42:05 +03:00
{
return new self($phrase);
2022-02-23 15:42:05 +03:00
}
/**
* The image contents
2022-02-23 15:42:05 +03:00
*/
public function getContents()
2022-02-23 15:42:05 +03:00
{
return $this->contents;
2022-02-23 15:42:05 +03:00
}
/**
* Enable/Disables the interpolation
*
* @param $interpolate bool True to enable, false to disable
*
* @return Captcha_builder
2022-02-23 15:42:05 +03:00
*/
public function setInterpolation($interpolate = true)
2022-02-23 15:42:05 +03:00
{
$this->interpolation = $interpolate;
2022-02-23 15:42:05 +03:00
return $this;
}
2022-02-23 15:42:05 +03:00
/**
* Enables/disable distortion
*/
public function setDistortion($distortion)
{
$this->distortion = (bool) $distortion;
return $this;
}
public function setMaxBehindLines($maxBehindLines)
{
$this->maxBehindLines = $maxBehindLines;
return $this;
}
public function setMaxFrontLines($maxFrontLines)
{
$this->maxFrontLines = $maxFrontLines;
return $this;
}
public function setMaxAngle($maxAngle)
{
$this->maxAngle = $maxAngle;
return $this;
}
public function setMaxOffset($maxOffset)
{
$this->maxOffset = $maxOffset;
return $this;
2022-02-23 15:42:05 +03:00
}
/**
* Sets the text color to use
2022-02-23 15:42:05 +03:00
*/
public function setTextColor($r, $g, $b)
2022-02-23 15:42:05 +03:00
{
$this->textColor = [$r, $g, $b];
2022-02-23 15:42:05 +03:00
return $this;
}
2022-02-23 15:42:05 +03:00
/**
* Sets the background color to use
*/
public function setBackgroundColor($r, $g, $b)
{
$this->backgroundColor = [$r, $g, $b];
2022-02-23 15:42:05 +03:00
return $this;
}
2022-02-23 15:42:05 +03:00
public function setLineColor($r, $g, $b)
{
$this->lineColor = [$r, $g, $b];
2022-02-23 15:42:05 +03:00
return $this;
2022-02-23 15:42:05 +03:00
}
/**
* Sets the ignoreAllEffects value
2022-02-23 15:42:05 +03:00
*
* @param bool $ignoreAllEffects
* @return Captcha_builder
2022-02-23 15:42:05 +03:00
*/
public function setIgnoreAllEffects($ignoreAllEffects)
2022-02-23 15:42:05 +03:00
{
$this->ignoreAllEffects = $ignoreAllEffects;
2022-02-23 15:42:05 +03:00
return $this;
2022-02-23 15:42:05 +03:00
}
/**
* Sets the list of background images to use (one image is randomly selected)
2022-02-23 15:42:05 +03:00
*/
public function setBackgroundImages(array $backgroundImages)
2022-02-23 15:42:05 +03:00
{
$this->backgroundImages = $backgroundImages;
return $this;
2022-02-23 15:42:05 +03:00
}
/**
* Builds while the code is readable against an OCR
2022-02-23 15:42:05 +03:00
*/
public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null)
2022-02-23 15:42:05 +03:00
{
do {
$this->build($width, $height, $font, $fingerprint);
} while ($this->isOCRReadable());
}
2022-02-23 15:42:05 +03:00
/**
* Try to read the code against an OCR
*/
public function isOCRReadable()
{
if (!is_dir($this->tempDir)) {
@mkdir($this->tempDir, 0755, true);
2022-02-23 15:42:05 +03:00
}
$tempj = $this->tempDir . uniqid('captcha', true) . '.jpg';
$tempp = $this->tempDir . uniqid('captcha', true) . '.pgm';
2022-02-23 15:42:05 +03:00
$this->save($tempj);
shell_exec("convert $tempj $tempp");
$value = trim(strtolower(shell_exec("ocrad $tempp")));
2022-02-23 15:42:05 +03:00
@unlink($tempj);
@unlink($tempp);
return $this->testPhrase($value);
2022-02-23 15:42:05 +03:00
}
/**
* Saves the Captcha to a jpeg file
2022-02-23 15:42:05 +03:00
*/
public function save($filename, $quality = 90)
2022-02-23 15:42:05 +03:00
{
imagejpeg($this->contents, $filename, $quality);
}
2022-02-23 15:42:05 +03:00
/**
* Returns true if the given phrase is good
*/
public function testPhrase($phrase)
{
return $this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase());
}
2022-02-23 15:42:05 +03:00
/**
* Gets the captcha phrase
*/
public function getPhrase()
{
return $this->phrase;
}
/**
* Setting the phrase
*/
public function setPhrase($phrase)
{
$this->phrase = (string) $phrase;
}
/**
* Gets the image GD
*/
public function getGd()
{
return $this->contents;
}
/**
* Gets the HTML inline base64
*/
public function inline($quality = 90)
{
return 'data:image/jpeg;base64,' . base64_encode($this->get($quality));
}
/**
* Gets the image contents
*/
public function get($quality = 90)
{
ob_start();
$this->output($quality);
return ob_get_clean();
}
/**
* Outputs the image
*/
public function output($quality = 90)
{
imagejpeg($this->contents, null, $quality);
}
/**
* @return array
*/
public function getFingerprint()
{
return $this->fingerprint;
2022-02-23 15:42:05 +03:00
}
}