feat(rgpd): política de privacidade e termos, mapa a pedido, fotos locais, retenção, tema claro/escuro

- 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>
This commit is contained in:
andreandClaude Opus 5.5 committed 2026-09-25 11:39:05 +01:00
1 parent 04c8c83e66
commit b202262da0
16 files changed
+297 -34

No files matched your search

+29
View File
@@ -216,6 +216,35 @@ function _trash_audit(PDO $pdo, string $action, string $type, int $id, string $s
]);
}
// ─── Retenção (política de privacidade, secção 3) ────────────────────────────
// Leads com mais de 12 meses vão para a lixeira (deleted_by NULL = sistema); a
// eliminação definitiva continua a exigir os 30 dias e um clique. Submissões
// recusadas pelo anti-spam e o outbox de segurança: apagados aos 90 dias.
// Chamado uma vez por dia pelo cron.php.
function trash_retention_sweep(PDO $pdo): array {
$done = [];
foreach (['trial_lead' => 'trial_leads', 'membership_lead' => 'membership_leads', 'event_lead' => 'event_leads'] as $type => $table) {
try {
$n = $pdo->exec("UPDATE `{$table}` SET deleted_at = NOW(), deleted_by = NULL
WHERE deleted_at IS NULL AND created_at < DATE_SUB(NOW(), INTERVAL 12 MONTH)");
if ($n) {
$done[$type] = (int) $n;
if (function_exists('platform_audit')) {
platform_audit($pdo, 'trash.retention', ['target_type' => $type, 'actor' => false,
'summary' => $n . ' registo(s) com mais de 12 meses enviados para a lixeira']);
}
}
} catch (Throwable $e) { error_log('[retention] ' . $table . ': ' . $e->getMessage()); }
}
foreach (['blocked_submissions' => 'created_at', 'hub_security_outbox' => 'occurred_at'] as $table => $col) {
try {
$n = $pdo->exec("DELETE FROM `{$table}` WHERE `{$col}` < DATE_SUB(NOW(), INTERVAL 90 DAY)");
if ($n) $done[$table] = (int) $n;
} catch (Throwable $e) { /* tabela ainda não existe */ }
}
return $done;
}
// ─── Efeitos em ficheiros ────────────────────────────────────────────────────
// O site público lista as fotos da galeria com glob() na pasta, não pela BD.
// Por isso uma imagem apagada tem de sair da pasta: vai para .trash/ (o