- Política de privacidade reescrita: tabela de dados/finalidades/fundamento
(inclui os pedidos de aula, inscrição e eventos com IP), prazos concretos,
subcontratantes, Google Maps e reviews, cookies. Termos e condições novos
(/home/termos), no rodapé e no sitemap. Campos [ENTRE PARÊNTESES RETOS]
e revisão por jurista pendentes antes de publicar no domínio real.
- Google Maps só carrega ao carregar em «Mostrar mapa» (map_click_to_load);
as fotos dos autores das reviews são guardadas pelo servidor em
uploads/reviews/ e servidas localmente (review_avatar_url).
- Retenção que a política declara, aplicada pelo cron (04:00): leads com
mais de 12 meses → lixeira; blocked_submissions e outbox de segurança
apagados aos 90 dias (trash_retention_sweep, auditado).
- Backoffice e login com tema claro/escuro/automático: data-bs-theme
aplicado no <head> antes do CSS (sem flash), escolha em localStorage,
segue o sistema em tempo real, color-scheme e theme-color para os dois.
Cores próprias passaram a variáveis do Bootstrap. lang="pt-PT".
Testado numa cópia da BD de produção: 72/72 verificações.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- shared/platform-tools: ea3616a → 12f26fd (27 commits). Traz o módulo de
audit (f761d99, o pin da frota), «manter sessão iniciada», o registo de
segurança da frota (o gancho que o login já tinha passa a funcionar) e a
correção do IP do cliente encontrada nesta auditoria (último X-Forwarded-For
/ X-Real-IP), promovida ao partilhado. client_ip() da app delega nele.
- config/functions_audit.php: audit() sobre platform_audit(); tabela
audit_log criada no primeiro uso, segredos ocultados, nunca parte a ação.
- Registado: login, falha de login, bloqueio por excesso de tentativas,
logout, pedidos de recuperação, mudança/definição de palavra-passe,
registo e criação de utilizadores, convites, perfil (antes/depois), todas
as definições (db_update_setting, com valor anterior), cada passo da
lixeira (apagar, restaurar, eliminar de vez) e ações nas mensagens.
- Sistema → Auditoria (views/sysadmin/audit.php), só sysadmin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Correções da auditoria de 24/09 (audits/fitme3070-auditoria-2026-09-24.html).
Lixeira
- Tudo o que é importante passa a soft delete (deleted_at + deleted_by):
equipa, modalidades, eventos, mensalidades, galerias, imagens, horário,
horas de funcionamento, inscrições em eventos e pedidos de inscrição.
- config/functions_trash.php: um registo por tipo; apagar, restaurar, listar
e eliminar leem todos dele. Eliminar de vez só com 30 dias na lixeira —
a condição está no WHERE do DELETE, um POST forjado não apaga nada.
- Gestão → Lixeira (views/admin/trash/index.php): lista única com quem e
quando, restauro a qualquer momento.
- Imagens de galeria: o site lista a pasta, por isso o ficheiro passa para
.trash/ (negado pelo .htaccess) e volta no restauro.
- As listagens e o site público filtram deleted_at (horário também filtra
modalidades apagadas). Migração: database/migration_20260925_trash.sql
(só acrescenta colunas; tem de ser aplicada antes deste código).
CSRF
- csrf_check() recusa qualquer pedido que não seja POST (405). Antes saía
sem verificar nada em GET, e dez ações de apagar/restaurar eram GET.
- Os dez handlers passaram a POST com token; links viraram formulários.
Autenticação e sessão
- client_ip(): X-Real-IP / último X-Forwarded-For, só vindo de proxy privado
com TRUSTED_PROXY. O throttle contava o IP do proxy (limite global).
- Throttle também por conta (hash do email), além do IP.
- Sem oráculo de tempo: login verifica contra um hash de referência quando
a conta não existe; a recuperação deixa o email na fila.
- Cookie de sessão Secure/HttpOnly/SameSite=Lax e use_strict_mode.
- Definições de sistema só para sysadmin; autocomplete=username.
Servidor e uploads
- .htaccess: nosniff, Referrer-Policy, X-Frame-Options, Permissions-Policy,
CSP (frame-ancestors, base-uri, object-src, form-action); nega parciais
_*.php, PHP soltos do platform-tools e *.md; 404 própria.
- Uploads validados pelo conteúdo (finfo/getimagesize); SVG só sem scripts,
handlers on*=, foreignObject ou hrefs externos.
Testado numa cópia da BD de produção (fitme_audit_test) com um contentor
temporário: 34/34 verificações HTTP ponta-a-ponta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>