diff --git a/config/functions.php b/config/functions.php index 26a22b7..e964b23 100644 --- a/config/functions.php +++ b/config/functions.php @@ -43,6 +43,7 @@ require_once __DIR__ . '/functions_reviews.php'; // reviews Google (Places require_once __DIR__ . '/functions_trials.php'; // pedidos de aula experimental require_once __DIR__ . '/functions_privacy.php'; // mapa e fotos da Google só a pedido / servidos localmente require_once __DIR__ . '/functions_forms.php'; // anti-spam + bloqueio de IP dos formulários públicos +require_once __DIR__ . '/functions_rgpd.php'; // pedidos de acesso/apagamento (art.º 15/17) // ─── DB-backed settings → legacy constants ─────────────────────────────────── // Resolved once from the settings cache so call sites read plain constants. diff --git a/config/functions_rgpd.php b/config/functions_rgpd.php new file mode 100644 index 0000000..3aaf684 --- /dev/null +++ b/config/functions_rgpd.php @@ -0,0 +1,74 @@ + '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]); +} diff --git a/database/migration_20260925_rgpd_requests.sql b/database/migration_20260925_rgpd_requests.sql new file mode 100644 index 0000000..18594b4 --- /dev/null +++ b/database/migration_20260925_rgpd_requests.sql @@ -0,0 +1,20 @@ +-- RGPD (art.º 15.º/17.º): pedidos de acesso e de apagamento de dados pessoais. +-- O visitante submete um pedido na Política de Privacidade; fica aqui (o email +-- está desligado, por isso o pedido é guardado, como as leads). O backoffice +-- processa: acesso = exportar os dados; apagamento = anonimizar. +CREATE TABLE IF NOT EXISTS `rgpd_requests` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `gym_id` INT NOT NULL DEFAULT 0, + `email` VARCHAR(190) NOT NULL, + `request_type` ENUM('acesso','apagamento') NOT NULL, + `message` TEXT NULL, + `status` ENUM('novo','concluido') NOT NULL DEFAULT 'novo', + `consent` TINYINT(1) NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `processed_at` DATETIME NULL DEFAULT NULL, + `processed_by` INT UNSIGNED NULL DEFAULT NULL, + `deleted_at` DATETIME NULL DEFAULT NULL, + `deleted_by` INT UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_rgpd_requests_status` (`status`, `created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/includes/header.php b/includes/header.php index a7634ee..0e4d3ef 100644 --- a/includes/header.php +++ b/includes/header.php @@ -173,6 +173,10 @@ $app_name = defined('APP_NAME') ? APP_NAME : 'FitMe 3070'; Lixeira +
O mapa da secção de contactos é do Google Maps e só é carregado se carregares em «Mostrar mapa»; nesse momento a Google recebe o teu endereço IP e pode guardar cookies, segundo a política da Google. 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.
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 = $pp_email !== '' ? $pp_email : '[EMAIL]' ?>; respondemos no prazo de um mês. +
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 (www.cnpd.pt).
+Este site usa apenas um cookie técnico de sessão (FIT_SESS), 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.
Pedidos de acesso (exportação) e apagamento feitos na Política de Privacidade. O apagamento anonimiza o nome/e-mail/telefone nas leads — a linha fica para estatística e audit.
+ +| Data | Pedido | Mensagem | Estado | Ações | +|
|---|---|---|---|---|---|
| Sem pedidos RGPD. | |||||
| = htmlspecialchars(format_date_pt((string) $r['created_at'])) ?> | += htmlspecialchars($r['email']) ?> | += $r['request_type'] === 'apagamento' ? 'Apagamento' : 'Acesso' ?> | += htmlspecialchars((string) $r['message']) ?> | += $r['status'] === 'novo' ? 'Novo' : 'Concluído' ?> | ++ Exportar + + + + | +