Version 0.7.0 #1
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]);
|
||||
}
|
||||
}
|
24
app/Http/Controllers/Api/V1/CaptchaController.php
Normal file
24
app/Http/Controllers/Api/V1/CaptchaController.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Resources\Api\V1\Captcha;
|
||||
use App\Services\Api\V1\CaptchaService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
final class CaptchaController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CaptchaService $captchaService
|
||||
) { }
|
||||
|
||||
public function getCaptcha(): JsonResponse
|
||||
{
|
||||
$result = $this->captchaService->generate();
|
||||
if (!$result->isSuccess()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new Captcha($result));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
31
app/Http/Resources/Api/V1/Captcha.php
Normal file
31
app/Http/Resources/Api/V1/Captcha.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1;
|
||||
|
||||
use App\ServiceResults\Api\V1\CaptchaService\Captcha as CaptchaResult;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
final class Captcha extends JsonResource
|
||||
{
|
||||
|
||||
/**
|
||||
* @var CaptchaResult
|
||||
*/
|
||||
public $resource;
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'image_base64' => $this->resource->getImageBase64(),
|
||||
'image_text_base64' => $this->resource->getImageTextBase64(),
|
||||
'captcha_key' => $this->resource->getKey()
|
||||
];
|
||||
}
|
||||
}
|
@ -1,17 +1,40 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Captcha\Contracts\ImageBody;
|
||||
use App\Captcha\Contracts\ImageHead;
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use App\Captcha\Contracts\ImageManager as ImageManagerContract;
|
||||
use App\Captcha\Images\Body;
|
||||
use App\Captcha\Images\Head;
|
||||
use App\Captcha\Images\ImageManager;
|
||||
use App\Captcha\Images\Lines;
|
||||
use App\Services\Api\V1\CaptchaService;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
final class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
$this->app->bind(ImageManagerContract::class, function () {
|
||||
return new ImageManager(imageClassName: config('captcha.imageClass'));
|
||||
});
|
||||
$this->app->bind(ImageHead::class, Head::class);
|
||||
$this->app->bind(ImageBody::class, Body::class);
|
||||
$this->app->bind(ImageLines::class, Lines::class);
|
||||
|
||||
$this->app->bind(CaptchaService::class, function (Application $app) {
|
||||
return new CaptchaService(
|
||||
config: config('captcha', []),
|
||||
imageHead: $app->make(ImageHead::class),
|
||||
imageBody: $app->make(ImageBody::class)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
38
app/ServiceResults/Api/V1/CaptchaService/Captcha.php
Normal file
38
app/ServiceResults/Api/V1/CaptchaService/Captcha.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults\Api\V1\CaptchaService;
|
||||
|
||||
use App\ServiceResults\ServiceResult;
|
||||
|
||||
final class Captcha extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $imageBase64,
|
||||
private readonly string $imageTextBase64,
|
||||
private readonly string $key
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getImageBase64(): string
|
||||
{
|
||||
return $this->imageBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getImageTextBase64(): string
|
||||
{
|
||||
return $this->imageTextBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
}
|
97
app/Services/Api/V1/CaptchaService.php
Normal file
97
app/Services/Api/V1/CaptchaService.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Api\V1;
|
||||
|
||||
use App\Captcha\Contracts\ImageBody;
|
||||
use App\Captcha\Contracts\ImageHead;
|
||||
use App\Captcha\Contracts\Type;
|
||||
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||
|
||||
final class CaptchaService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $config,
|
||||
private readonly ImageHead $imageHead,
|
||||
private readonly ImageBody $imageBody
|
||||
) { }
|
||||
|
||||
public function generate(): ServiceResultError | Captcha
|
||||
{
|
||||
$types = $this->config['types'] ?? [];
|
||||
if (empty($types)) {
|
||||
$error = __('No captcha type settings!');
|
||||
report($error);
|
||||
return $this->errService($error);
|
||||
}
|
||||
|
||||
try {
|
||||
$type = Arr::random($types);
|
||||
|
||||
/** @var Type $captcha */
|
||||
$typeCaptcha = new $type['class'](
|
||||
$type['params'] ?? []
|
||||
);
|
||||
$symbols = $typeCaptcha->getSymbols();
|
||||
|
||||
$imageHeadConfig = $this->makeImageHeadConfig($this->config['image_head'] ?? []);
|
||||
$imageHead = $this->imageHead->processing($symbols, $imageHeadConfig);
|
||||
unset($imageHeadConfig);
|
||||
|
||||
$imageBodyConfig = $this->makeImageBodyConfig($this->config['image_body'] ?? []);
|
||||
$imageBody = $this->imageBody->processing($symbols, $imageBodyConfig);
|
||||
unset($imageBodyConfig);
|
||||
|
||||
|
||||
dd($imageHead, $imageBody);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService('Captcha service error!');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return new Captcha(
|
||||
imageBase64: $imageHead->getImage()->getImageBase64(),
|
||||
imageTextBase64: $imageBody->getImage()->getImageBase64(),
|
||||
key: 'dddd'
|
||||
);
|
||||
}
|
||||
|
||||
private function makeImageHeadConfig(array $params): ImageHeadConfig
|
||||
{
|
||||
return new ImageHeadConfig(
|
||||
backgrounds: $params['backgrounds'] ?? [],
|
||||
fonts: $params['fonts'] ?? [],
|
||||
fontColors: $params['font_colors'] ?? [],
|
||||
width: $params['width'] ?? 150,
|
||||
height: $params['height'] ?? 40,
|
||||
textPaddingTop: $params['text_padding_top'] ?? 5,
|
||||
textPaddingLeft: $params['text_padding_left'] ?? 10,
|
||||
angle: $params['angle'] ?? 20,
|
||||
numberLines: $params['number_lines'] ?? 3,
|
||||
lineColors: $params['line_colors'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private function makeImageBodyConfig(array $params): ImageBodyConfig
|
||||
{
|
||||
return new ImageBodyConfig(
|
||||
backgrounds: $params['backgrounds'] ?? [],
|
||||
fonts: $params['fonts'] ?? [],
|
||||
fontColors: $params['font_colors'] ?? [],
|
||||
width: $params['width'] ?? 300,
|
||||
height: $params['height'] ?? 240,
|
||||
angle: $params['angle'] ?? 20,
|
||||
fontSize: $params['font_size'] ?? [20, 50],
|
||||
numberLines: $params['number_lines'] ?? 3,
|
||||
lineColors: $params['line_colors'] ?? []
|
||||
);
|
||||
}
|
||||
}
|
54
app/Services/Service.php
Normal file
54
app/Services/Service.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
|
||||
abstract class Service
|
||||
{
|
||||
final protected function errValidate(string $message, array $errors = []): ServiceResultError
|
||||
{
|
||||
return $this->error(422, $message, $errors);
|
||||
}
|
||||
|
||||
final protected function errFobidden(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(403, $message);
|
||||
}
|
||||
|
||||
final protected function errNotFound(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(404, $message);
|
||||
}
|
||||
|
||||
final protected function errService(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(500, $message);
|
||||
}
|
||||
|
||||
final protected function notAcceptable(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(406, $message);
|
||||
}
|
||||
|
||||
final protected function ok(string $message = 'OK'): ServiceResultArray
|
||||
{
|
||||
return $this->result(['message' => $message]);
|
||||
}
|
||||
|
||||
final protected function result(array $data = []): ServiceResultArray
|
||||
{
|
||||
return new ServiceResultArray(data: $data);
|
||||
}
|
||||
|
||||
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
|
||||
{
|
||||
return new ServiceResultError(
|
||||
message: $message,
|
||||
errors: $errors,
|
||||
code: $code
|
||||
);
|
||||
}
|
||||
}
|
@ -13,3 +13,5 @@ use Illuminate\Support\Facades\Route;
|
||||
| be assigned to the "api" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
Route::get('/captcha', [\App\Http\Controllers\Api\V1\CaptchaController::class, 'getCaptcha']);
|
||||
|
Loading…
Reference in New Issue
Block a user