fbd4e92e9f
Full rewrite: swap out the v1 framework (src/, controllers/, views/, twig/, sass/, skeleton/) for the working v2 codebase from phpproject (App/, novaconium/, public/).
52 lines
1.7 KiB
PHP
52 lines
1.7 KiB
PHP
<?php
|
|
|
|
namespace Lib;
|
|
|
|
/**
|
|
* Self-hosted spam detection for any form sidecar: a honeypot field plus
|
|
* a submission-timing check. No external CAPTCHA service, no CDN script,
|
|
* no site key/secret key, no outbound API call — see /admin/docs/sidecars's
|
|
* "Spam prevention" section for the full write-up and App/pages/contact/
|
|
* for a working example.
|
|
*
|
|
* Pair this with a hidden honeypot input (named per $honeypotField below,
|
|
* hidden off-screen via the .hp-field CSS class — not display:none, since
|
|
* some bots specifically skip fields hidden that way) and a hidden
|
|
* `renderedAt()`-valued timestamp field in the form's Twig template.
|
|
*/
|
|
final class SpamGuard
|
|
{
|
|
public function __construct(
|
|
private readonly string $honeypotField = 'website',
|
|
private readonly string $timestampField = 'rendered_at',
|
|
private readonly int $minSeconds = 2,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Call this when rendering the form (i.e. in the sidecar's return
|
|
* array, GET or POST) and put the result in a hidden field named
|
|
* $timestampField for isSpam() to read back on submit.
|
|
*/
|
|
public function renderedAt(): int
|
|
{
|
|
return time();
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $post typically $_POST
|
|
*/
|
|
public function isSpam(array $post): bool
|
|
{
|
|
$honeypotFilled = trim((string) ($post[$this->honeypotField] ?? '')) !== '';
|
|
|
|
$renderedAt = (int) ($post[$this->timestampField] ?? 0);
|
|
// Not cryptographically signed, so a determined bot could forge
|
|
// this — it's a deterrent against unsophisticated spam, not a
|
|
// security boundary.
|
|
$tooFast = $renderedAt === 0 || (time() - $renderedAt) < $this->minSeconds;
|
|
|
|
return $honeypotFilled || $tooFast;
|
|
}
|
|
}
|