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:
1 parent
116817e2de
commit
3f2c505c17
6 files changed
+248
-1
No files matched your search
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// RGPD — pedidos de acesso (art.º 15.º) e apagamento (art.º 17.º).
|
||||
//
|
||||
// Os formulários públicos guardam PII em membership_leads, trial_leads e
|
||||
// event_leads, sempre identificável pelo email. O "apagamento" aqui é
|
||||
// anonimização: a linha fica (estatística/audit), mas o nome/email/telefone
|
||||
// passam a placeholders — o dado pessoal deixa de existir sem quebrar nada.
|
||||
|
||||
function rgpd_pii_tables(): array {
|
||||
return [
|
||||
'membership_leads' => 'Pedidos de inscrição',
|
||||
'trial_leads' => 'Aulas experimentais',
|
||||
'event_leads' => 'Inscrições em eventos',
|
||||
];
|
||||
}
|
||||
|
||||
// Devolve os dados pessoais associados a um email, organizados por tabela.
|
||||
function rgpd_find(PDO $pdo, string $email): array {
|
||||
$out = [];
|
||||
foreach (rgpd_pii_tables() as $table => $label) {
|
||||
$st = $pdo->prepare(
|
||||
"SELECT name, email, phone, message, created_at FROM `$table` WHERE email = ? ORDER BY created_at DESC"
|
||||
);
|
||||
$st->execute([$email]);
|
||||
$rows = $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows) $out[$table] = $rows;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// Anonimiza os dados pessoais de um email em todas as tabelas públicas.
|
||||
// Devolve o número de registos alterados.
|
||||
function rgpd_erase(PDO $pdo, string $email): int {
|
||||
$n = 0;
|
||||
foreach (array_keys(rgpd_pii_tables()) as $table) {
|
||||
$st = $pdo->prepare(
|
||||
"UPDATE `$table` SET name = 'Apagado (RGPD)', email = '', phone = '', message = '' WHERE email = ?"
|
||||
);
|
||||
$st->execute([$email]);
|
||||
$n += $st->rowCount();
|
||||
}
|
||||
return $n;
|
||||
}
|
||||
|
||||
function db_rgpd_request_create(PDO $pdo, array $d): int {
|
||||
$st = $pdo->prepare(
|
||||
"INSERT INTO rgpd_requests (gym_id, email, request_type, message, consent) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
$st->execute([
|
||||
(int) ($d['gym_id'] ?? 0),
|
||||
(string) $d['email'],
|
||||
(string) $d['request_type'],
|
||||
(string) ($d['message'] ?? ''),
|
||||
(int) ($d['consent'] ?? 0),
|
||||
]);
|
||||
return (int) $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
function db_rgpd_request_get(PDO $pdo, int $id): ?array {
|
||||
$st = $pdo->prepare("SELECT * FROM rgpd_requests WHERE id = ? LIMIT 1");
|
||||
$st->execute([$id]);
|
||||
return $st->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function db_rgpd_requests_list(PDO $pdo): array {
|
||||
return $pdo->query(
|
||||
"SELECT * FROM rgpd_requests WHERE deleted_at IS NULL ORDER BY created_at DESC"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function db_rgpd_request_mark(PDO $pdo, int $id, string $status, int $by): void {
|
||||
$st = $pdo->prepare("UPDATE rgpd_requests SET status = ?, processed_at = NOW(), processed_by = ? WHERE id = ?");
|
||||
$st->execute([$status, $by, $id]);
|
||||
}
|
||||
Reference in new issue
Block a user