Files
roi-theme/Shared/Domain/Entities/RecaptchaResult.php
FrankZamora d135ec8a41 feat(api): implement recaptcha v3 anti-spam protection
- Add RecaptchaValidatorInterface and RecaptchaResult entity in Domain
- Create RecaptchaValidationService in Application layer
- Implement GoogleRecaptchaValidator for API integration
- Add recaptcha-settings schema and admin FormBuilder
- Integrate reCAPTCHA validation in NewsletterAjaxHandler
- Integrate reCAPTCHA validation in ContactFormAjaxHandler
- Update FooterRenderer and ContactFormRenderer with reCAPTCHA scripts
- Configure DIContainer with RecaptchaValidationService injection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 17:01:46 -06:00

135 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace ROITheme\Shared\Domain\Entities;
/**
* RecaptchaResult - Entidad que representa el resultado de validacion reCAPTCHA
*
* RESPONSABILIDAD: Encapsular el resultado de la validacion de reCAPTCHA v3
* incluyendo el score, estado de exito y posibles codigos de error.
*
* INMUTABILIDAD: Esta entidad es inmutable - una vez creada no puede modificarse.
*
* SCORE reCAPTCHA v3:
* - 1.0: Muy probablemente humano
* - 0.9: Probablemente humano
* - 0.5: Indeterminado
* - 0.1: Probablemente bot
* - 0.0: Muy probablemente bot
*
* @package ROITheme\Shared\Domain\Entities
*/
final class RecaptchaResult
{
/**
* @param bool $success Si la validacion fue exitosa (token valido)
* @param float $score Score de 0.0 (bot) a 1.0 (humano)
* @param string $action Accion verificada
* @param array<string> $errorCodes Codigos de error de la API
* @param string $hostname Hostname donde se genero el token
* @param string $challengeTs Timestamp del challenge
*/
public function __construct(
private bool $success,
private float $score,
private string $action,
private array $errorCodes = [],
private string $hostname = '',
private string $challengeTs = ''
) {}
/**
* Verificar si el resultado es valido segun un threshold
*
* @param float $threshold Score minimo requerido (ej: 0.5)
* @return bool True si success=true Y score >= threshold
*/
public function isValid(float $threshold): bool
{
return $this->success && $this->score >= $threshold;
}
/**
* Verificar si la accion coincide con la esperada
*
* @param string $expectedAction Accion esperada
* @return bool True si la accion coincide
*/
public function hasValidAction(string $expectedAction): bool
{
return $this->action === $expectedAction;
}
public function isSuccess(): bool
{
return $this->success;
}
public function getScore(): float
{
return $this->score;
}
public function getAction(): string
{
return $this->action;
}
/**
* @return array<string>
*/
public function getErrorCodes(): array
{
return $this->errorCodes;
}
public function hasErrors(): bool
{
return !empty($this->errorCodes);
}
public function getHostname(): string
{
return $this->hostname;
}
public function getChallengeTs(): string
{
return $this->challengeTs;
}
/**
* Crear resultado de fallo (para errores de red, timeout, etc.)
*
* @param array<string> $errorCodes Codigos de error
* @return self
*/
public static function failure(array $errorCodes = ['unknown-error']): self
{
return new self(
success: false,
score: 0.0,
action: '',
errorCodes: $errorCodes
);
}
/**
* Convertir a array para logging
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'success' => $this->success,
'score' => $this->score,
'action' => $this->action,
'error_codes' => $this->errorCodes,
'hostname' => $this->hostname,
'challenge_ts' => $this->challengeTs,
];
}
}