Add captcha image generation functionality.
This commit is contained in:
144
app/Captcha/Config/ImageBody.php
Normal file
144
app/Captcha/Config/ImageBody.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Config;
|
||||
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final readonly class ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private array $backgrounds,
|
||||
private array $fonts,
|
||||
private array $fontColors,
|
||||
private int $width = 300,
|
||||
private int $height = 240,
|
||||
private int $angle = 20,
|
||||
private array | int $fontSize = [20, 50],
|
||||
private int $numberLines = 3,
|
||||
private array $lineColors = []
|
||||
) {
|
||||
if ($this->width <= 0) {
|
||||
throw new CaptchaException('Incorrect $width settings.');
|
||||
}
|
||||
if ($this->height <= 0) {
|
||||
throw new CaptchaException('Incorrect $height settings.');
|
||||
}
|
||||
if (count($this->backgrounds) < 1) {
|
||||
throw new CaptchaException('Invalid $backgrounds parameter.');
|
||||
}
|
||||
if (count($this->fonts) < 1) {
|
||||
throw new CaptchaException('Invalid $fonts parameter.');
|
||||
}
|
||||
if (count($this->fontColors) < 1) {
|
||||
throw new CaptchaException('Invalid $fontColors parameter.');
|
||||
}
|
||||
if ($this->angle < 0) {
|
||||
throw new CaptchaException('Incorrect $angle settings.');
|
||||
}
|
||||
|
||||
if (!is_integer($this->fontSize) && !is_array($this->fontSize)) {
|
||||
throw new CaptchaException('The $fontSize parameter is not an array or integer.');
|
||||
} elseif (is_array($this->fontSize)) {
|
||||
if (count($this->fontSize) > 2) {
|
||||
throw new CaptchaException('The array of $fontSize parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($this->fontSize[0] > $this->fontSize[1]) {
|
||||
throw new CaptchaException('The number of $fontSize[1] is less than $fontSize[0].');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBackgrounds(): array
|
||||
{
|
||||
return $this->backgrounds;
|
||||
}
|
||||
|
||||
public function randomBackground(): string
|
||||
{
|
||||
return Arr::random($this->getBackgrounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFonts(): array
|
||||
{
|
||||
return $this->fonts;
|
||||
}
|
||||
|
||||
public function randomFont(): string
|
||||
{
|
||||
return Arr::random($this->getFonts());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFontColors(): array
|
||||
{
|
||||
return $this->fontColors;
|
||||
}
|
||||
|
||||
public function randomFontColor(): string
|
||||
{
|
||||
return Arr::random($this->getFontColors());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAngle(): int
|
||||
{
|
||||
return $this->angle;
|
||||
}
|
||||
|
||||
public function randomAngle(): int
|
||||
{
|
||||
return mt_rand($this->getAngle() * -1, $this->getAngle());
|
||||
}
|
||||
|
||||
public function getNumberLines(): int
|
||||
{
|
||||
return $this->numberLines;
|
||||
}
|
||||
|
||||
public function fontSize(): int
|
||||
{
|
||||
$fontSize = $this->fontSize;
|
||||
if (is_integer($fontSize)) {
|
||||
return $fontSize;
|
||||
}
|
||||
if (empty($fontSize[1])) {
|
||||
return $fontSize[0];
|
||||
}
|
||||
return random_int($fontSize[0], $fontSize[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLineColors(): array
|
||||
{
|
||||
return $this->lineColors;
|
||||
}
|
||||
}
|
147
app/Captcha/Config/ImageHead.php
Normal file
147
app/Captcha/Config/ImageHead.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Config;
|
||||
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final readonly class ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private array $backgrounds,
|
||||
private array $fonts,
|
||||
private array $fontColors,
|
||||
private int $width = 150,
|
||||
private int $height = 40,
|
||||
private int $textPaddingTop = 5,
|
||||
private int $textPaddingLeft = 10,
|
||||
private int $angle = 20,
|
||||
private int $numberLines = 3,
|
||||
private array $lineColors = []
|
||||
) {
|
||||
if ($this->width <= 0) {
|
||||
throw new CaptchaException('Incorrect $width settings.');
|
||||
}
|
||||
if ($this->height <= 0) {
|
||||
throw new CaptchaException('Incorrect $height settings.');
|
||||
}
|
||||
if ($this->textPaddingTop < 0) {
|
||||
throw new CaptchaException('Incorrect $textPaddingTop settings.');
|
||||
}
|
||||
if ($this->textPaddingLeft < 0) {
|
||||
throw new CaptchaException('Incorrect $textPaddingLeft settings.');
|
||||
}
|
||||
if (count($this->backgrounds) < 1) {
|
||||
throw new CaptchaException('Invalid $backgrounds parameter.');
|
||||
}
|
||||
if (count($this->fonts) < 1) {
|
||||
throw new CaptchaException('Invalid $fonts parameter.');
|
||||
}
|
||||
if (count($this->fontColors) < 1) {
|
||||
throw new CaptchaException('Invalid $fontColors parameter.');
|
||||
}
|
||||
if ($this->angle < 0) {
|
||||
throw new CaptchaException('Incorrect $angle settings.');
|
||||
}
|
||||
if ($this->numberLines < 0) {
|
||||
throw new CaptchaException('Incorrect $numberLines settings.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBackgrounds(): array
|
||||
{
|
||||
return $this->backgrounds;
|
||||
}
|
||||
|
||||
public function randomBackground(): string
|
||||
{
|
||||
return Arr::random($this->getBackgrounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFonts(): array
|
||||
{
|
||||
return $this->fonts;
|
||||
}
|
||||
|
||||
public function randomFont(): string
|
||||
{
|
||||
return Arr::random($this->getFonts());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFontColors(): array
|
||||
{
|
||||
return $this->fontColors;
|
||||
}
|
||||
|
||||
public function randomFontColor(): string
|
||||
{
|
||||
return Arr::random($this->getFontColors());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTextPaddingTop(): int
|
||||
{
|
||||
return $this->textPaddingTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTextPaddingLeft(): int
|
||||
{
|
||||
return $this->textPaddingLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAngle(): int
|
||||
{
|
||||
return $this->angle;
|
||||
}
|
||||
|
||||
public function randomAngle(): int
|
||||
{
|
||||
return mt_rand($this->getAngle() * -1, $this->getAngle());
|
||||
}
|
||||
|
||||
public function getNumberLines(): int
|
||||
{
|
||||
return $this->numberLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLineColors(): array
|
||||
{
|
||||
return $this->lineColors;
|
||||
}
|
||||
}
|
12
app/Captcha/Contracts/ImageBody.php
Normal file
12
app/Captcha/Contracts/ImageBody.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||
use App\Captcha\Dto\ImageBody as ImageBodyDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
interface ImageBody
|
||||
{
|
||||
public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto;
|
||||
}
|
12
app/Captcha/Contracts/ImageHead.php
Normal file
12
app/Captcha/Contracts/ImageHead.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Dto\ImageHead as ImageHeadDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||
|
||||
interface ImageHead
|
||||
{
|
||||
public function processing(Symbols $symbols, ImageHeadConfig $config): ImageHeadDto;
|
||||
}
|
10
app/Captcha/Contracts/Type.php
Normal file
10
app/Captcha/Contracts/Type.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
interface Type
|
||||
{
|
||||
public function getSymbols(): Symbols;
|
||||
}
|
89
app/Captcha/Dto/Coordinators.php
Normal file
89
app/Captcha/Dto/Coordinators.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Coordinators
|
||||
{
|
||||
public function __construct(
|
||||
private int $x1,
|
||||
private int $y1,
|
||||
private int $x2,
|
||||
private int $y2,
|
||||
private int $x3,
|
||||
private int $y3,
|
||||
private int $x4,
|
||||
private int $y4
|
||||
) { }
|
||||
|
||||
/**
|
||||
* lower left x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX1(): int
|
||||
{
|
||||
return $this->x1;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower left y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY1(): int
|
||||
{
|
||||
return $this->y1;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower right x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX2(): int
|
||||
{
|
||||
return $this->x2;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower right y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY2(): int
|
||||
{
|
||||
return $this->y2;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper right x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX3(): int
|
||||
{
|
||||
return $this->x3;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper right y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY3(): int
|
||||
{
|
||||
return $this->y3;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper left x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX4(): int
|
||||
{
|
||||
return $this->x4;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper left y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY4(): int
|
||||
{
|
||||
return $this->y4;
|
||||
}
|
||||
}
|
36
app/Captcha/Dto/Image.php
Normal file
36
app/Captcha/Dto/Image.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Image
|
||||
{
|
||||
public function __construct(
|
||||
private string $imageBase64,
|
||||
private int $width,
|
||||
private int $height
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getImageBase64(): string
|
||||
{
|
||||
return $this->imageBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
27
app/Captcha/Dto/ImageBody.php
Normal file
27
app/Captcha/Dto/ImageBody.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private Image $image,
|
||||
private array $coordinators
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return Image
|
||||
*/
|
||||
public function getImage(): Image
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Coordinators
|
||||
*/
|
||||
public function getCoordinators(): array
|
||||
{
|
||||
return $this->coordinators;
|
||||
}
|
||||
}
|
18
app/Captcha/Dto/ImageHead.php
Normal file
18
app/Captcha/Dto/ImageHead.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private Image $image
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return Image
|
||||
*/
|
||||
public function getImage(): Image
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
}
|
45
app/Captcha/Dto/Sector.php
Normal file
45
app/Captcha/Dto/Sector.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Sector
|
||||
{
|
||||
public function __construct(
|
||||
private int $x,
|
||||
private int $y,
|
||||
private int $width,
|
||||
private int $height,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getX(): int
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getY(): int
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
24
app/Captcha/Dto/Sectors.php
Normal file
24
app/Captcha/Dto/Sectors.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final class Sectors
|
||||
{
|
||||
private array $points = [];
|
||||
|
||||
public function add(int|float $x, int|float $y, int|float $width, int|float $height): self
|
||||
{
|
||||
$this->points[] = new Sector((int) $x, (int) $y, (int) $width, (int) $height);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function random(): Sector
|
||||
{
|
||||
$key = array_rand($this->points);
|
||||
$sector = $this->points[$key];
|
||||
unset($this->points[$key]);
|
||||
|
||||
return $sector;
|
||||
}
|
||||
}
|
38
app/Captcha/Dto/Symbols.php
Normal file
38
app/Captcha/Dto/Symbols.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
|
||||
final readonly class Symbols
|
||||
{
|
||||
public function __construct(
|
||||
private array $success,
|
||||
private array $fakes,
|
||||
private SymbolType $type
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSuccess(): array
|
||||
{
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFakes(): array
|
||||
{
|
||||
return $this->fakes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SymbolType
|
||||
*/
|
||||
public function getType(): SymbolType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
8
app/Captcha/Enums/SymbolType.php
Normal file
8
app/Captcha/Enums/SymbolType.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Enums;
|
||||
|
||||
Enum SymbolType {
|
||||
case String;
|
||||
case ImagePath;
|
||||
}
|
8
app/Captcha/Exceptions/CaptchaException.php
Normal file
8
app/Captcha/Exceptions/CaptchaException.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Exceptions;
|
||||
|
||||
final class CaptchaException extends \Exception
|
||||
{
|
||||
|
||||
}
|
117
app/Captcha/Images/Body.php
Normal file
117
app/Captcha/Images/Body.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use App\Captcha\Dto\Coordinators;
|
||||
use App\Captcha\Dto\Image as ImageDto;
|
||||
use App\Captcha\Dto\ImageBody as ImageBodyDto;
|
||||
use App\Captcha\Dto\Sector;
|
||||
use App\Captcha\Dto\Sectors;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Contracts\ImageBody;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Contracts\ImageManager;
|
||||
|
||||
final class Body implements ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImageManager $imageManager,
|
||||
private readonly ImageLines $imageLines
|
||||
) { }
|
||||
|
||||
public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto
|
||||
{
|
||||
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
|
||||
$image->insertBackground($config->randomBackground());
|
||||
|
||||
$imageData = match ($symbols->getType()) {
|
||||
SymbolType::String => $this->processingString($image, $symbols, $config),
|
||||
};
|
||||
$image = $imageData['image'];
|
||||
|
||||
if ($config->getNumberLines() > 0) {
|
||||
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
|
||||
}
|
||||
|
||||
$image = new ImageDto(
|
||||
imageBase64: $image->encode(),
|
||||
width: $image->getWidth(),
|
||||
height: $image->getHeight()
|
||||
);
|
||||
|
||||
return new ImageBodyDto(
|
||||
image: $image,
|
||||
coordinators: $imageData['coordinators']
|
||||
);
|
||||
}
|
||||
|
||||
private function processingString(Image $image, Symbols $symbols, ImageBodyConfig $config): array
|
||||
{
|
||||
$sectors = $this->calculateSectors($symbols, $image->getWidth(), $image->getHeight());
|
||||
$coordinators = [];
|
||||
foreach ($symbols->getSuccess() as $number => $symbol) {
|
||||
$coordinators[] = $this->processingStringSymbol($image, $symbol, $config, $sectors->random());
|
||||
}
|
||||
|
||||
if (!empty($symbols->getFakes())) {
|
||||
foreach ($symbols->getFakes() as $number => $symbol) {
|
||||
$this->processingStringSymbol($image, $symbol, $config, $sectors->random());
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'image' => $image,
|
||||
'coordinators' => $coordinators
|
||||
];
|
||||
}
|
||||
|
||||
private function processingStringSymbol(Image &$image, string|int $symbol, ImageBodyConfig $config, Sector $sector): Coordinators
|
||||
{
|
||||
$fontSize = $config->fontSize();
|
||||
if ($fontSize > $sector->getHeight()) {
|
||||
$fontSize = $sector->getHeight();
|
||||
}
|
||||
$fontPathFile = $config->randomFont();
|
||||
$fontColor = $config->randomFontColor();
|
||||
$angle = $config->randomAngle();
|
||||
$marginLeft = mt_rand($sector->getX(), $sector->getX() + $sector->getWidth() - $fontSize);
|
||||
$marginTop = mt_rand($sector->getY(), $sector->getY() + $sector->getHeight() - $fontSize);
|
||||
|
||||
return $image->addText(
|
||||
text: $symbol,
|
||||
x: $marginLeft,
|
||||
y: $marginTop,
|
||||
size: $fontSize,
|
||||
angle: $angle,
|
||||
hexColor: $fontColor,
|
||||
fontName: $fontPathFile
|
||||
);
|
||||
}
|
||||
|
||||
private function calculateSectors(Symbols $symbols, int $width, int $height): Sectors
|
||||
{
|
||||
$points = count($symbols->getSuccess()) + count($symbols->getFakes());
|
||||
$sumFloors = 3;
|
||||
$sumRooms = ceil($points / $sumFloors);
|
||||
|
||||
$heightFloor = floor($height / $sumFloors);
|
||||
$widthFloor = floor($width / $sumRooms);
|
||||
|
||||
$sectors = new Sectors();
|
||||
for ($floor = 0; $floor < $sumFloors; $floor++) {
|
||||
$y = $heightFloor * $floor;
|
||||
for ($room = 0; $room < $sumRooms; $room++) {
|
||||
$sectors->add(
|
||||
x: ($widthFloor * $room),
|
||||
y: $y,
|
||||
width: $widthFloor,
|
||||
height: $heightFloor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sectors;
|
||||
}
|
||||
}
|
72
app/Captcha/Images/Head.php
Normal file
72
app/Captcha/Images/Head.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use App\Captcha\Dto\Image as ImageDto;
|
||||
use App\Captcha\Dto\ImageHead as ImageHeadDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Contracts\ImageHead;
|
||||
use \App\Captcha\Contracts\Image as ImageContract;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Contracts\ImageManager;
|
||||
|
||||
final class Head implements ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImageManager $imageManager,
|
||||
private readonly ImageLines $imageLines
|
||||
) { }
|
||||
|
||||
public function processing(Symbols $symbols, ImageHeadConfig $config): ImageHeadDto
|
||||
{
|
||||
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
|
||||
$image->insertBackground($config->randomBackground());
|
||||
|
||||
$image = match ($symbols->getType()) {
|
||||
SymbolType::String => $this->processingString($image, $symbols, $config),
|
||||
};
|
||||
if ($config->getNumberLines() > 0) {
|
||||
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
|
||||
}
|
||||
|
||||
$image = new ImageDto(
|
||||
imageBase64: $image->encode(),
|
||||
width: $image->getWidth(),
|
||||
height: $image->getHeight()
|
||||
);
|
||||
|
||||
return new ImageHeadDto(image: $image);
|
||||
}
|
||||
|
||||
private function processingString(ImageContract $image, Symbols $symbols, ImageHeadConfig $config): Image
|
||||
{
|
||||
$textLeftPadding = $config->getTextPaddingLeft();
|
||||
$textTopPadding = $config->getTextPaddingTop();
|
||||
$countSymbols = count($symbols->getSuccess());
|
||||
$widthPrefix = $image->getWidth() - $textLeftPadding;
|
||||
$imageHeight = $image->getHeight() - $textTopPadding;
|
||||
|
||||
foreach ($symbols->getSuccess() as $number => $symbol) {
|
||||
$marginLeft = $textLeftPadding + $number * $widthPrefix / $countSymbols;
|
||||
|
||||
$image->addText(
|
||||
text: $symbol,
|
||||
x: intval($marginLeft),
|
||||
y: $textTopPadding,
|
||||
size: $this->randomFontSize($imageHeight),
|
||||
angle: $config->randomAngle(),
|
||||
hexColor: $config->randomFontColor(),
|
||||
fontName: $config->randomFont()
|
||||
);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function randomFontSize(int $imageHeight): int
|
||||
{
|
||||
return mt_rand($imageHeight - 10, $imageHeight);
|
||||
}
|
||||
}
|
26
app/Captcha/Type.php
Normal file
26
app/Captcha/Type.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha;
|
||||
|
||||
use App\Captcha\Contracts\Type as TypeContract;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
abstract class Type implements TypeContract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $config
|
||||
) { }
|
||||
|
||||
abstract public function getSymbols(): Symbols;
|
||||
|
||||
final public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
final public function getConfigValue(string | int $name, mixed $default = null): mixed
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
return $config[$name] ?? $default;
|
||||
}
|
||||
}
|
104
app/Captcha/Types/StringType.php
Normal file
104
app/Captcha/Types/StringType.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Types;
|
||||
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use App\Captcha\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final class StringType extends Type
|
||||
{
|
||||
|
||||
public function getSymbols(): Symbols
|
||||
{
|
||||
$lengthSymbols = $this->lengthSymbols();
|
||||
$lengthFakeSymbols = $this->lengthFakeSymbols();
|
||||
|
||||
$success = $this->randomSymbols($lengthSymbols);
|
||||
$fakes = [];
|
||||
if (!empty($lengthFakeSymbols)) {
|
||||
$fakes = $this->randomSymbols($lengthFakeSymbols, $fakes);
|
||||
}
|
||||
|
||||
return new Symbols(
|
||||
success: $success,
|
||||
fakes: $fakes,
|
||||
type: SymbolType::String
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @param array $except
|
||||
* @return array
|
||||
* @throws CaptchaException
|
||||
*/
|
||||
private function randomSymbols(int $lenght, array $except = []): array
|
||||
{
|
||||
$symbols = $this->symbols();
|
||||
if (!empty($except)) {
|
||||
$symbols = array_diff($symbols, $except);
|
||||
}
|
||||
|
||||
if (count($symbols) < $lenght) {
|
||||
throw new CaptchaException('The number of characters is less than $lenght.');
|
||||
}
|
||||
|
||||
return Arr::random($symbols, $lenght);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws CaptchaException
|
||||
*/
|
||||
private function symbols(): array
|
||||
{
|
||||
$symbols = $this->getConfigValue('symbols');
|
||||
if (!is_array($symbols)) {
|
||||
throw new CaptchaException('The symbols parameter is not an array.');
|
||||
}
|
||||
return $symbols;
|
||||
}
|
||||
|
||||
private function lengthSymbols(): int
|
||||
{
|
||||
$length = $this->getConfigValue('length_symbols');
|
||||
if (is_integer($length)) {
|
||||
return $length;
|
||||
}
|
||||
if (!is_array($length)) {
|
||||
throw new CaptchaException('The length_symbols parameter is not an array or integer.');
|
||||
}
|
||||
if (count($length) > 2) {
|
||||
throw new CaptchaException('The array of length_symbols parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($length[0] > $length[1]) {
|
||||
throw new CaptchaException('The number of length_symbols[1] is less than length_symbols[0].');
|
||||
}
|
||||
return random_int($length[0], $length[1]);
|
||||
}
|
||||
|
||||
private function lengthFakeSymbols(): int
|
||||
{
|
||||
$length = $this->getConfigValue('length_fake_symbols');
|
||||
if (is_integer($length)) {
|
||||
return $length;
|
||||
}
|
||||
if (is_null($length) || $length === false) {
|
||||
return 0;
|
||||
}
|
||||
if (!is_array($length)) {
|
||||
throw new CaptchaException('The length_fake_symbols parameter is not an array or integer or null or false.');
|
||||
}
|
||||
if (count($length) > 2) {
|
||||
throw new CaptchaException('The array of length_fake_symbols parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($length[0] > $length[1]) {
|
||||
throw new CaptchaException('The number of length_fake_symbols[1] is less than length_fake_symbols[0].');
|
||||
}
|
||||
return random_int($length[0], $length[1]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user