'', 'subtitle' => '', 'price' => '', 'is_addon' => 0, 'price_suffix' => '/mês', 'features' => '', 'is_featured' => 0, 'featured_label' => 'Mais popular', 'cta_label' => 'Inscrever', 'item_order' => 0, 'is_active' => 1, ]; } function db_memberships_list(PDO $pdo, bool $include_deleted = false): array { $sql = "SELECT * FROM memberships"; if (!$include_deleted) $sql .= " WHERE deleted_at IS NULL"; $sql .= " ORDER BY deleted_at IS NOT NULL, item_order, name"; return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); } // Public list: active, not deleted. Defensive — the table may not exist yet on // an install that hasn't run the migration. function site_memberships(PDO $pdo): array { try { return $pdo->query( "SELECT * FROM memberships WHERE deleted_at IS NULL AND is_active = 1 ORDER BY item_order, name" )->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { return []; } } function db_memberships_get(PDO $pdo, int $id): ?array { $stmt = $pdo->prepare("SELECT * FROM memberships WHERE id = ? LIMIT 1"); $stmt->execute([$id]); return $stmt->fetch(PDO::FETCH_ASSOC) ?: null; } // Normalise raw POST into the row shape; appends messages to $errors. function memberships_from_post(array $post, array &$errors): array { $price = str_replace([' ', '€'], '', trim((string) ($post['price'] ?? ''))); $price = str_replace(',', '.', $price); $data = [ 'name' => mb_substr(trim((string) ($post['name'] ?? '')), 0, 100), 'subtitle' => mb_substr(trim((string) ($post['subtitle'] ?? '')), 0, 255), 'price' => $price, 'is_addon' => !empty($post['is_addon']) ? 1 : 0, 'price_suffix' => mb_substr(trim((string) ($post['price_suffix'] ?? '')), 0, 60), 'features' => trim((string) ($post['features'] ?? '')), 'is_featured' => !empty($post['is_featured']) ? 1 : 0, 'featured_label' => mb_substr(trim((string) ($post['featured_label'] ?? '')), 0, 40), 'cta_label' => mb_substr(trim((string) ($post['cta_label'] ?? '')), 0, 40), 'item_order' => (int) ($post['item_order'] ?? 0), 'is_active' => !empty($post['is_active']) ? 1 : 0, ]; if ($data['name'] === '') $errors[] = 'O nome é obrigatório.'; if ($price !== '' && (!is_numeric($price) || (float) $price < 0)) $errors[] = 'Preço inválido — usa um número, ex.: 34,90.'; if ($data['featured_label'] === '') $data['featured_label'] = 'Mais popular'; if ($data['cta_label'] === '') $data['cta_label'] = 'Inscrever'; return $data; } function _memberships_params(array $d): array { return [ $d['name'], $d['subtitle'], $d['price'] === '' || $d['price'] === null ? null : number_format((float) $d['price'], 2, '.', ''), $d['is_addon'], $d['price_suffix'], $d['features'] !== '' ? $d['features'] : null, $d['is_featured'], $d['featured_label'], $d['cta_label'], $d['item_order'], $d['is_active'], ]; } function _memberships_clear_featured(PDO $pdo, int $except): void { $pdo->prepare("UPDATE memberships SET is_featured = 0 WHERE id <> ? AND is_featured = 1")->execute([$except]); } function db_memberships_create(PDO $pdo, array $d): int { $pdo->beginTransaction(); $stmt = $pdo->prepare(" INSERT INTO memberships (name, subtitle, price, is_addon, price_suffix, features, is_featured, featured_label, cta_label, item_order, is_active, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW()) "); $stmt->execute(_memberships_params($d)); $id = (int) $pdo->lastInsertId(); if ($d['is_featured']) _memberships_clear_featured($pdo, $id); $pdo->commit(); return $id; } function db_memberships_update(PDO $pdo, int $id, array $d): bool { $pdo->beginTransaction(); $stmt = $pdo->prepare(" UPDATE memberships SET name = ?, subtitle = ?, price = ?, is_addon = ?, price_suffix = ?, features = ?, is_featured = ?, featured_label = ?, cta_label = ?, item_order = ?, is_active = ?, updated_at = NOW() WHERE id = ? "); $params = _memberships_params($d); $params[] = $id; $stmt->execute($params); $changed = $stmt->rowCount() > 0; if ($d['is_featured']) _memberships_clear_featured($pdo, $id); $pdo->commit(); return $changed; } function db_memberships_delete(PDO $pdo, int $id): bool { $stmt = $pdo->prepare("UPDATE memberships SET deleted_at = NOW(), is_featured = 0 WHERE id = ? AND deleted_at IS NULL"); $stmt->execute([$id]); return $stmt->rowCount() > 0; } function db_memberships_restore(PDO $pdo, int $id): bool { $stmt = $pdo->prepare("UPDATE memberships SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL"); $stmt->execute([$id]); return $stmt->rowCount() > 0; } // "34,90 €" / "+10 €" / "Sob consulta" — PT formatting, drops ",00". function membership_price_label(array $m): string { if ($m['price'] === null || $m['price'] === '') return 'Sob consulta'; $p = (float) $m['price']; $s = number_format($p, floor($p) == $p ? 0 : 2, ',', ' '); return (!empty($m['is_addon']) ? '+' : '') . $s . ' €'; } // Non-empty trimmed lines of a textarea — used for features, skills, formações. function text_lines(?string $text): array { $out = []; foreach (preg_split('/\R/', (string) $text) as $line) { $line = trim($line, " \t-•"); if ($line !== '') $out[] = $line; } return $out; } // ─── Pedidos de inscrição (leads) ───────────────────────────────────────────── function membership_lead_statuses(): array { return ['novo' => 'Novo', 'contactado' => 'Contactado', 'inscrito' => 'Inscrito', 'descartado' => 'Descartado']; } function db_membership_leads_list(PDO $pdo, string $status = ''): array { $sql = "SELECT * FROM membership_leads"; $args = []; if ($status !== '' && isset(membership_lead_statuses()[$status])) { $sql .= " WHERE status = ?"; $args[] = $status; } $sql .= " ORDER BY created_at DESC, id DESC"; $stmt = $pdo->prepare($sql); $stmt->execute($args); return $stmt->fetchAll(PDO::FETCH_ASSOC); } function db_membership_leads_count_new(PDO $pdo): int { try { return (int) $pdo->query("SELECT COUNT(*) FROM membership_leads WHERE status = 'novo'")->fetchColumn(); } catch (Throwable $e) { return 0; } } function db_membership_lead_create(PDO $pdo, array $d): int { $stmt = $pdo->prepare(" INSERT INTO membership_leads (membership_id, membership_name, name, email, phone, message, consent, consent_text, ip, user_agent, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?, NOW(), NOW()) "); $stmt->execute([ $d['membership_id'] ?: null, mb_substr((string) ($d['membership_name'] ?? ''), 0, 100), mb_substr(trim((string) ($d['name'] ?? '')), 0, 150), mb_substr(trim((string) ($d['email'] ?? '')), 0, 190), mb_substr(trim((string) ($d['phone'] ?? '')), 0, 40), mb_substr(trim((string) ($d['message'] ?? '')), 0, 1000), !empty($d['consent']) ? 1 : 0, mb_substr((string) ($d['consent_text'] ?? ''), 0, 255), mb_substr((string) ($d['ip'] ?? ''), 0, 45), mb_substr((string) ($d['user_agent'] ?? ''), 0, 255), ]); return (int) $pdo->lastInsertId(); } function db_membership_lead_update(PDO $pdo, int $id, string $status, string $notes): bool { if (!isset(membership_lead_statuses()[$status])) return false; $stmt = $pdo->prepare("UPDATE membership_leads SET status = ?, admin_notes = ?, updated_at = NOW() WHERE id = ?"); $stmt->execute([$status, $notes !== '' ? $notes : null, $id]); return $stmt->rowCount() > 0; } function db_membership_lead_delete(PDO $pdo, int $id): bool { $stmt = $pdo->prepare("DELETE FROM membership_leads WHERE id = ?"); $stmt->execute([$id]); return $stmt->rowCount() > 0; }