Files
nexusphp/app/Services/Captcha/CaptchaManager.php
Qi HU 9033eff8ea feat: Refine captcha configuration and drivers
Introduce a configurable captcha manager with drivers for image,
Cloudflare Turnstile, and Google reCAPTCHA, including fallback
behaviour.

Refactor login, signup, complain, and related flows to use the new
abstraction while simplifying the legacy image endpoint.

Document captcha environment options and restore classic defaults in
.env.example.

Signed-off-by: Qi HU <github@spcsky.com>
2025-10-11 23:38:27 +08:00

121 lines
3.4 KiB
PHP

<?php
namespace App\Services\Captcha;
use App\Services\Captcha\Exceptions\CaptchaValidationException;
use Illuminate\Support\Arr;
class CaptchaManager
{
/** @var array<string, CaptchaDriverInterface> */
protected array $drivers = [];
protected ?array $config = null;
public function driver(?string $name = null): CaptchaDriverInterface
{
$name = $name ?? $this->getDefaultDriver();
$driver = $this->getDriverInstance($name);
if ($name !== 'image' && !$driver->isEnabled()) {
return $this->driver('image');
}
return $driver;
}
public function render(array $context = []): string
{
return $this->driver()->render($context);
}
public function verify(array $payload, array $context = []): bool
{
try {
return $this->driver()->verify($payload, $context);
} catch (CaptchaValidationException $exception) {
throw $exception;
}
}
public function isEnabled(): bool
{
return $this->driver()->isEnabled();
}
protected function getDriverInstance(string $name): CaptchaDriverInterface
{
if (!isset($this->drivers[$name])) {
try {
$this->drivers[$name] = $this->resolveDriver($name);
} catch (\InvalidArgumentException $exception) {
if ($name !== 'image') {
return $this->getDriverInstance('image');
}
throw $exception;
}
}
return $this->drivers[$name];
}
protected function resolveDriver(string $name): CaptchaDriverInterface
{
$config = $this->getConfigValue("drivers.$name", []);
if (!is_array($config) || empty($config)) {
throw new \InvalidArgumentException("Captcha driver [$name] is not defined.");
}
$driverClass = Arr::get($config, 'class');
if (!$driverClass || !class_exists($driverClass)) {
throw new \InvalidArgumentException("Captcha driver class for [$name] is invalid.");
}
$driver = new $driverClass($config);
if (!$driver instanceof CaptchaDriverInterface) {
throw new \InvalidArgumentException("Captcha driver [$name] must implement " . CaptchaDriverInterface::class);
}
return $driver;
}
protected function getDefaultDriver(): string
{
return (string) $this->getConfigValue('default', 'image');
}
protected function getConfigValue(string $key, $default = null)
{
if ($this->config === null) {
$config = null;
if (function_exists('app')) {
try {
$repository = app('config');
if ($repository) {
$config = $repository->get('captcha');
}
} catch (\Throwable $exception) {
$config = null;
}
}
if (!is_array($config) && function_exists('nexus_config')) {
$config = nexus_config('captcha', []);
}
if (!is_array($config)) {
$path = (defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 3) . DIRECTORY_SEPARATOR) . 'config/captcha.php';
$config = is_file($path) ? require $path : [];
}
$this->config = is_array($config) ? $config : [];
}
return Arr::get($this->config, $key, $default);
}
}