Fecha os itens 3.2.2, 3.3.1, 4.6, 5.2, 5.6, 5.7 e 7.4 do relatório de
auditoria de 2026-09-24: escapa saídas de site_img_url()/canonical,
valida identificadores de coluna em db_info_save(), troca 100vh por
100dvh, remove a última cor solta, encurta a transição para ~150ms,
acrescenta decoding=async/fetchpriority=high nas imagens da home, e
documenta as versões vendorizadas em ASSETS.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
Estrutura multi-ginásio: as mesmas secções do site, conteúdo próprio por
ginásio, e cada um numa pasta da URL com o código postal da zona.
- migration_20260926_gyms: tabela gyms (pasta, nome, localidade, publicado,
principal, secções visíveis, título/imagem do header, SEO, galeria, Place ID,
página CrossFit) com o FitMe 3070 como ginásio 1 em /3070/; gym_id em team,
modalidades (o horário segue a modalidade), work_hours, memberships,
membership_leads e website_offer (tudo o existente → ginásio 1); events com
gym_id NULL = todos; fitme3070_info passa a uma linha por ginásio (id = gym).
- Site: /<pasta>/, /<pasta>/crossfit, /<pasta>/agendar|inscricao. Ginásio por
publicar → 404 para o público, pré-visualização (com faixa) para a equipa.
/ e /home → 301 para o ginásio principal (a manter a query); com mais de um
ginásio publicado, a raiz passa a uma página para escolher. Secções
desligadas não aparecem nem no menu; menu, canonical, JSON-LD, sitemap,
formulários, e-mails e reviews (Place ID e cache) são do ginásio.
- Backoffice: Gestão → Ginásios (criar/editar/apagar, secções, e «começar
com o conteúdo de» outro ginásio — cópias independentes) e o seletor
«A editar» no topo; equipa, modalidades, horários, horário de
funcionamento, mensalidades, pedidos, contactos e reviews trabalham sobre o
ginásio escolhido, e um registo de outro ginásio não abre.
- Modalidades passam para logo a seguir à galeria das instalações.
- Galeria sem cantos arredondados (raio da marca).
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>
- Ícones novos (Bootcamp, YourFit Core, Run3070, Total Workout, On Ramp, Jump)
do mesmo conjunto; só o LPO continua sem ícone no ficheiro de design.
- Os ícones fitme-*.svg passam a ir embutidos (modalidade_icon_html): cada
traço tem pathLength=1 e desenha-se ao aparecer, no hover e ao abrir o
cartão; cada família tem um movimento (levantar, soco, respirar, saltar,
chama, pedalar, correr). Outros ícones continuam <img>.
- No cartão de detalhe (desktop) o ícone e o nome deslizam até ao centro
vertical da coluna.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Mensalidades: ao passar o rato, a borda do cartão ganha um brilho verde num
conic-gradient que roda (@property --pc-a animável), centrado no cursor; o
cartão inclina-se e desliza para o rato como os da equipa.
- «O que dizem de nós» e rodapé: textura discreta de metal preto mate (grão de
pintura eletrostática, brilho difuso, riscas quase invisíveis), em SVG/CSS.
- Contactos: o site lê só fitme3070_info (Gestão → Contactos). Os campos
contact_*/social_* de Sistema → Definições não eram usados e foram editados
por engano — saem do formulário, com um aviso a apontar para Contactos.
site_info() deixa o WhatsApp só com dígitos (wa.me não aceita + nem espaços).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Horário de aulas: cartões mais pequenos e estreitos; a partir de 992px mostra
o dia anterior, hoje ao centro e o seguinte (a dar a volta à semana), com as
horas alinhadas e os dias laterais esbatidos. O JPG continua a semana toda.
- Horário de funcionamento: cartões compactos numa <dl>, ordenados por hora.
- Equipa: fundo no cinzento da parede do ginásio com juntas de blocos e grão
muito discretos (SVG inline).
- Tira o «FitMe 3070» do header e esconde a newsletter.
- Estrutura: um <main> em todas as páginas públicas (skip link aponta-lhe),
h2 por secção com aria-labelledby, h3 nos itens em vez de h4, sem secções
aninhadas, alts distintos na galeria e no h1 do CrossFit.
- robots.txt e sitemap.xml gerados com o domínio da instalação; SEO_NOINDEX
no .env tira uma cópia de staging dos motores de pesquisa.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Contactos: nova página no backoffice (fitme3070_info) e 964 201 137 como
telefone principal; o telefone secundário só aparece quando existe.
- Equipa: título, aptidões/funções e formações; o cartão mostra foto, nome e
título e abre um cartão horizontal de detalhe (hover no desktop, toque no
telemóvel) sem mexer na grelha.
- Mensalidades: cartões editáveis com um destaque, modal de inscrição que
guarda o pedido (RGPD + estado + campos reservados para o CRM) e avisa o
ginásio por e-mail; lista de pedidos com estados, notas e CSV.
- Reviews Google via Places API (New), filtradas por classificação mínima e
em cache configurável; página de configuração com pesquisa do Place ID.
- Assets com ?v=<mtime>: site/assets é servido com cache imutável de 1 ano.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plain-PHP fleet platform with id.andmore.pt SSO. Wires shared/platform-tools
as a submodule (platform_update, backups, git/migration tools) like the other
platforms.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>