feat(rgpd): pedido de acesso/apagamento de dados + processo escrito (item 9.5)

- migration_20260925_rgpd_requests.sql: tabela rgpd_requests (email, tipo,
  estado, consentimento).
- config/functions_rgpd.php: rgpd_find() (acesso, art.º 15) e rgpd_erase()
  (apagamento por anonimização, art.º 17) sobre membership_leads/trial_leads/
  event_leads, + CRUD dos pedidos.
- Política de Privacidade: formulário público (POST + CSRF + anti-spam) para
  pedir acesso ou apagamento, e o processo escrito (resposta em 30 dias).
- Backoffice: página "Pedidos RGPD" (nav + index) para exportar os dados de um
  email (CSV, audit) ou anonimizá-los (POST + CSRF, audit).

O email está desligado, por isso os pedidos ficam guardados (como as leads),
não dependem de SMTP. Texto legal com placeholders — rever com jurista antes
do go-live (como já constava na Política).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
andreandClaude Fable 5 committed 2026-09-25 23:48:18 +01:00
1 parent 116817e2de
commit 3f2c505c17
6 files changed
+248 -1

No files matched your search

+62 -1
View File
@@ -11,6 +11,36 @@ require __DIR__ . '/_open.php';
$pp_name = htmlspecialchars($info['name'] ?? 'FitMe 3070');
$pp_email = htmlspecialchars($info['email'] ?? '');
$pp_addr = htmlspecialchars($info['adress'] ?? '');
// Pedido RGPD (acesso/apagamento). POST + CSRF + anti-spam; fica guardado
// (o email está desligado, não pode depender de SMTP) e é processado no
// backoffice (Sistema → Pedidos RGPD).
$rgpd_status = '';
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
csrf_check();
$rgpd_email = trim((string) ($_POST['rgpd_email'] ?? ''));
$rgpd_type = (string) ($_POST['rgpd_type'] ?? '') === 'apagamento' ? 'apagamento' : 'acesso';
$rgpd_msg = trim((string) ($_POST['rgpd_message'] ?? ''));
$rgpd_consent = !empty($_POST['rgpd_consent']);
if (!filter_var($rgpd_email, FILTER_VALIDATE_EMAIL) || !$rgpd_consent) {
$rgpd_status = 'erro';
} elseif (public_form_screen($pdo, 'rgpd', 'rgpd_requests', $rgpd_email, $rgpd_msg) !== null) {
$rgpd_status = 'ok'; // anti-spam: mesma resposta de sucesso
} else {
try {
db_rgpd_request_create($pdo, [
'gym_id' => (int) $gym['id'],
'email' => $rgpd_email,
'request_type' => $rgpd_type,
'message' => $rgpd_msg,
'consent' => 1,
]);
$rgpd_status = 'ok';
} catch (Throwable $e) {
$rgpd_status = 'erro';
}
}
}
?>
<section class="privacy-section" style="padding: 140px 0 60px;">
<div class="container">
@@ -59,10 +89,41 @@ $pp_addr = htmlspecialchars($info['adress'] ?? '');
<p>O mapa da secção de contactos é do <b>Google Maps</b> e só é carregado se carregares em «Mostrar mapa»; nesse momento a Google recebe o teu endereço IP e pode guardar cookies, segundo a <a href="https://policies.google.com/privacy" target="_blank" rel="noopener">política da Google</a>. As avaliações apresentadas vêm do perfil público do ginásio no Google; as fotos dos autores são guardadas neste site para o teu navegador não ter de as pedir à Google.</p>
<h2 class="h4 mt-4">5. Os teus direitos</h2>
<p>Tens direito de acesso, retificação, apagamento, limitação, portabilidade e oposição, e de retirar o consentimento a qualquer momento (sem afetar o tratamento feito até aí). Basta escreveres para <a href="mailto:<?= $pp_email ?>"><?= $pp_email !== '' ? $pp_email : '[EMAIL]' ?></a>; respondemos no prazo de um mês.
<p>Tens direito de acesso, retificação, apagamento, limitação, portabilidade e oposição, e de retirar o consentimento a qualquer momento (sem afetar o tratamento feito até aí). Podes pedir a exportação dos teus dados ou o seu apagamento com o formulário abaixo; respondemos no prazo de um mês.
Se tiveres conta na área de cliente, podes corrigir os teus dados e preferências no teu perfil.
Tens ainda o direito de apresentar reclamação à Comissão Nacional de Proteção de Dados (<a href="https://www.cnpd.pt" target="_blank" rel="noopener">www.cnpd.pt</a>).</p>
<h3 class="h6 mt-4 mb-2">Pedir acesso ou apagamento</h3>
<?php if ($rgpd_status === 'ok'): ?>
<div class="alert alert-success py-2">Pedido recebido. Vamos responder no prazo de um mês.</div>
<?php elseif ($rgpd_status === 'erro'): ?>
<div class="alert alert-danger py-2">Não foi possível enviar o pedido. Confirma o e-mail e tenta de novo.</div>
<?php endif; ?>
<form method="POST" action="" class="mt-2 mb-3" style="max-width:520px;">
<?= public_form_trap('rgpd') /* honeypot + carimbo + prova de JS */ ?>
<?= csrf_field() ?>
<div class="mb-2">
<label class="form-label" for="rgpd_email">O teu e-mail</label>
<input type="email" class="form-control" id="rgpd_email" name="rgpd_email" required>
</div>
<div class="mb-2">
<label class="form-label" for="rgpd_type">O que pretendes?</label>
<select class="form-select" id="rgpd_type" name="rgpd_type">
<option value="acesso">Aceder aos meus dados (exportação)</option>
<option value="apagamento">Apagar os meus dados</option>
</select>
</div>
<div class="mb-2">
<label class="form-label" for="rgpd_message">Mensagem (opcional)</label>
<textarea class="form-control" id="rgpd_message" name="rgpd_message" rows="2"></textarea>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="rgpd_consent" name="rgpd_consent" value="1" required>
<label class="form-check-label" for="rgpd_consent">Consinto no tratamento dos meus dados para responder a este pedido.</label>
</div>
<button type="submit" class="btn btn-primary">Enviar pedido</button>
</form>
<h2 class="h4 mt-4">6. Cookies</h2>
<p>Este site usa apenas um cookie técnico de sessão (<code>FIT_SESS</code>), necessário para os formulários e para a área reservada, que expira ao fechar o navegador. Não usamos cookies de publicidade nem de estatística. O Google Maps só coloca cookies se escolheres ver o mapa.</p>