Files
fitme3070.pt/config/functions_forms.php
T
andreandClaude Opus 5.5 04c8c83e66 feat: aulas experimentais guardadas, anti-spam partilhado nos formulários, audit das escritas
- Pedido de aula experimental passa a ficar em trial_leads (por ginásio)
  ANTES do email: sem SMTP o visitante já não vê «erro» e o pedido não se
  perde. Gestão → Aulas experimentais: estados, notas, CSV (auditado),
  apagar para a lixeira; aviso «sem email» quando o aviso não saiu.
- Aula experimental, inscrição e eventos usam o anti-spam do platform-tools
  (honeypot, carimbo assinado com tempo mínimo, prova de JS, limite por IP,
  pontuação de conteúdo) e o bloqueio de IP, em vez de honeypots locais.
  Recusas ficam em blocked_submissions; resposta igual à de sucesso.
  Falha aberto se o módulo rebentar.
- Checkbox de consentimento da aula experimental passa a ser guardada.
- Audit das escritas de conteúdo: criar/editar equipa, modalidades,
  eventos, mensalidades, pedidos de inscrição, galerias, horário (incl.
  arrastar no calendário), horas, contactos e ginásios, com antes/depois
  campo a campo (audit_row/audit_change). Exportação CSV de inscrições
  auditada.
- .table-responsive em todas as tabelas do backoffice.
- Migração: database/migration_20260926_trial_leads.sql (só cria tabelas).

Testado numa cópia da BD de produção: 63/63 verificações HTTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:33:24 +01:00

48 lines
2.4 KiB
PHP

<?php
// ─── Formulários públicos: anti-spam e bloqueio de IP (platform-tools) ──────
// Aula experimental, inscrição e eventos usam as defesas partilhadas da frota
// em vez de um honeypot local por formulário:
// - platform_ip_guard(): um IP banido não chega ao formulário;
// - platform_antispam_check(): honeypot, carimbo assinado com tempo mínimo
// (time-trap), prova de JavaScript, limite por IP e pontuação de conteúdo.
// Uma submissão recusada recebe a mesma resposta de sucesso (o bot não aprende
// nada) e fica em `blocked_submissions`, para um falso positivo se poder ler.
//
// Falha aberto: se o módulo rebentar, o formulário continua a funcionar —
// segurança que derruba o formulário é uma avaria, não uma proteção.
require_once __DIR__ . '/../shared/platform-tools/functions_ip_ban.php';
require_once __DIR__ . '/../shared/platform-tools/functions_antispam_forms.php';
platform_antispam_config([
// NÃO mudar num site a correr: invalida os formulários abertos nesse momento.
'key_domain' => 'fitme-antispam|',
'log_table' => 'blocked_submissions',
'keywords' => ['seo', 'backlink', 'backlinks', 'crypto', 'bitcoin', 'casino', 'viagra', 'loan',
'guest post', 'web design', 'website traffic', 'ranking', 'forex', 'investment opportunity'],
// Marcas do português: quem as usa não é pontuado como «texto estrangeiro».
'native' => '/\b(ol[aá]|bom dia|boa (tarde|noite)|gostaria|queria|aulas?|gin[aá]sio|treino|obrigad[oa]|marcar|experimental|hor[aá]rio)\b/u',
]);
// Devolve o motivo da recusa (já registado) ou null para seguir.
function public_form_screen(PDO $pdo, string $form, string $table, string $name, string $text): ?string {
try {
platform_ip_guard($pdo); // sai com 403 se o IP estiver banido
return platform_antispam_check($pdo, $form, $table, ['name' => $name, 'text' => $text]);
} catch (Throwable $e) {
error_log('[antispam] ' . $form . ': ' . $e->getMessage());
return null;
}
}
// Os campos escondidos que o formulário tem de levar (honeypot + carimbo +
// prova de JS). Sem eles o carimbo falha e a submissão é recusada.
function public_form_trap(string $form): string {
try {
return platform_antispam_trap_field($form, $GLOBALS['pdo'] ?? null);
} catch (Throwable $e) {
error_log('[antispam] trap ' . $form . ': ' . $e->getMessage());
return '';
}
}