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>
This commit is contained in:
1 parent
0e8b53f620
commit
04c8c83e66
29 files changed
+480
-29
No files matched your search
@@ -23,3 +23,37 @@ function audit_mask_email(string $email): string {
|
||||
[$u, $d] = explode('@', $email, 2);
|
||||
return mb_substr($u, 0, 1) . '…@' . $d;
|
||||
}
|
||||
|
||||
// ─── Escritas de conteúdo ────────────────────────────────────────────────────
|
||||
// Os modelos (config/functions_*.php) chamam audit_row() antes de alterar e
|
||||
// audit_change() depois: o registo leva o antes/depois campo a campo
|
||||
// (platform_audit_diff) ou, numa criação, os campos criados. Tabela e chave vêm
|
||||
// do código, nunca do pedido, e ainda assim são validadas.
|
||||
|
||||
function audit_row(PDO $pdo, string $table, string $pk, int $id): ?array {
|
||||
if (!preg_match('/^[A-Za-z0-9_]+$/', $table) || !preg_match('/^[A-Za-z0-9_]+$/', $pk)) return null;
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT * FROM `{$table}` WHERE `{$pk}` = ? LIMIT 1");
|
||||
$st->execute([$id]);
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC);
|
||||
return $row ?: null;
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function audit_change(PDO $pdo, string $entity, string $table, string $pk, int $id, string $verb,
|
||||
?array $before = null, string $labelCol = 'name'): void {
|
||||
if (!function_exists('platform_audit') || $id < 1) return;
|
||||
$after = audit_row($pdo, $table, $pk, $id) ?? [];
|
||||
$changes = $before !== null
|
||||
? platform_audit_diff($before, $after, ['updated_at', 'created_at', 'updated_by'])
|
||||
: platform_audit_created(array_diff_key($after, array_flip(['created_at', 'updated_at'])));
|
||||
if ($verb !== 'create' && $before !== null && !$changes) return; // gravou sem mudar nada
|
||||
platform_audit($pdo, $entity . '.' . $verb, [
|
||||
'target_type' => $entity,
|
||||
'target_id' => $id,
|
||||
'target_label' => (string) ($after[$labelCol] ?? $before[$labelCol] ?? ''),
|
||||
'changes' => $changes,
|
||||
]);
|
||||
}
|
||||
Reference in new issue
Block a user