- 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>
60 lines
3.0 KiB
PHP
60 lines
3.0 KiB
PHP
<?php
|
|
// ─── Registo de auditoria ────────────────────────────────────────────────────
|
|
// O registo é o do platform-tools (shared/platform-tools/functions_audit.php):
|
|
// tabela audit_log criada no primeiro uso, autor e IP tirados da sessão e do
|
|
// proxy, segredos ocultados em `changes`, nunca parte a ação. Consulta em
|
|
// Sistema → Auditoria (views/sysadmin/audit.php).
|
|
//
|
|
// audit() é só um atalho que vai buscar o $pdo global, para as funções de
|
|
// modelo que não o recebem. Convenção das ações: <entidade>.<verbo>
|
|
// (team.update, trash.purge, auth.login_failed, export.membership_leads…).
|
|
|
|
require_once __DIR__ . '/../shared/platform-tools/functions_audit.php';
|
|
|
|
function audit(string $action, array $d = []): void {
|
|
platform_audit($GLOBALS['pdo'] ?? null, $action, $d);
|
|
}
|
|
|
|
// Email mascarado para registos de falhas: permite ver padrões (o mesmo
|
|
// endereço atacado muitas vezes) sem guardar o endereço de quem se enganou.
|
|
function audit_mask_email(string $email): string {
|
|
$email = strtolower(trim($email));
|
|
if (!str_contains($email, '@')) return $email === '' ? '' : mb_substr($email, 0, 1) . '…';
|
|
[$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,
|
|
]);
|
|
}
|