0, 'path' => '/', 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax', ]); session_name('PETBCSESS'); session_start(); } } function jsonResponse($data, int $status = 200): void { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; } function cleanText($v): string { $s = is_string($v) ? $v : strval($v ?? ''); // remove BOM + \r + aspas externas $s = preg_replace('/^\xEF\xBB\xBF/', '', $s); $s = str_replace("\r", '', $s); $s = trim($s); $s = preg_replace('/^"+|"+$/', '', $s); return trim($s); } function normalizeHeader(string $h): string { $h = mb_strtolower($h, 'UTF-8'); // remove acentos $h = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $h); $h = preg_replace('/[^a-z0-9]/', '', $h); return $h; } function normalizeProntuarioExact($p): string { return cleanText($p); } function extractAlojamentoNumber(string $alojamento): ?int { // aceita 4 ou 5 dígitos (ex.: 2901, 3901, 11001, 31016) if (preg_match('/\b\d{4,5}\b/', $alojamento, $m)) { return intval($m[0]); } return null; } function readJsonFile(string $path, $default) { try { if (!file_exists($path)) return $default; $raw = file_get_contents($path); $data = json_decode($raw, true); if ($data === null) return $default; return $data; } catch (Throwable $e) { return $default; } } function writeJsonAtomic(string $path, $data): void { $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } $tmp = $path . '.tmp'; file_put_contents($tmp, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); rename($tmp, $path); } function requireAdmin(): void { ensureSession(); if (empty($_SESSION['isAdmin'])) { jsonResponse(['error' => 'Sessão expirada ou não autenticada'], 401); } } function getCsrfToken(): string { ensureSession(); if (empty($_SESSION['csrfToken'])) { $_SESSION['csrfToken'] = bin2hex(random_bytes(32)); } return $_SESSION['csrfToken']; } function requireCsrf(): void { $token = $_SERVER['HTTP_CSRF_TOKEN'] ?? ($_POST['csrfToken'] ?? ''); $token = cleanText($token); $expected = getCsrfToken(); if (!$token || !hash_equals($expected, $token)) { jsonResponse(['error' => 'CSRF inválido'], 403); } } function verifyAdminPassword(string $password): bool { $password = strval($password); $hash = defined('ADM_PASSWORD_HASH') ? ADM_PASSWORD_HASH : ''; $plain = defined('ADM_PASSWORD_PLAIN') ? ADM_PASSWORD_PLAIN : ''; if (!empty($hash)) { return password_verify($password, $hash); } if (!empty($plain)) { return hash_equals($plain, $password); } return false; } // Cronograma (visitas) $CRONOGRAMA_VISITAS = [ 1 => ['1' => 'Domingo', '2' => 'Sexta-Feira', '3' => 'Sábado'], 2 => ['1' => 'Domingo', '2' => 'Sábado', '3' => 'Sexta-Feira'], 3 => ['1' => 'Sábado', '2' => 'Sexta-Feira', '3' => 'Domingo'], 4 => ['1' => 'Sábado', '2' => 'Domingo', '3' => 'Sexta-Feira'], 5 => ['1' => 'Domingo', '2' => 'Sexta-Feira', '3' => 'Sábado'], 6 => ['1' => 'Domingo', '2' => 'Sábado', '3' => 'Sexta-Feira'], 7 => ['1' => 'Sábado', '2' => 'Sexta-Feira', '3' => 'Domingo'], 8 => ['1' => 'Sábado', '2' => 'Domingo', '3' => 'Sexta-Feira'], 9 => ['1' => 'Domingo', '2' => 'Sexta-Feira', '3' => 'Sábado'], 10 => ['1' => 'Domingo', '2' => 'Sábado', '3' => 'Sexta-Feira'], 11 => ['1' => 'Sábado', '2' => 'Sexta-Feira', '3' => 'Domingo'], 12 => ['1' => 'Sábado', '2' => 'Domingo', '3' => 'Sexta-Feira'], ]; function getDiaVisitaPorBloco(string $alojamento): string { global $CRONOGRAMA_VISITAS; if (!$alojamento) return '—'; if (!preg_match('/\d/', $alojamento, $m)) return '—'; $bloco = $m[0]; $mes = intval(date('n')); $dias = $CRONOGRAMA_VISITAS[$mes] ?? null; return ($dias && isset($dias[$bloco])) ? $dias[$bloco] : '—'; } function buildPublicResponse(array $record): array { $prontuario = $record['prontuario'] ?? ''; $alojamento = $record['alojamento'] ?? ''; $n = extractAlojamentoNumber($alojamento); $msg = 'Favor entrar em contato com a equipe do serviço social para ver o dia de visita'; // 11001 a 11016: visita normal -> dia normal if ($n !== null && $n >= 11001 && $n <= 11016) { return ['prontuario' => $prontuario, 'diaVisita' => getDiaVisitaPorBloco($alojamento)]; } // 1901 a 1916 if ($n !== null && $n >= 1901 && $n <= 1916) { return ['prontuario' => $prontuario, 'mensagem' => $msg]; } // 21001 a 21016 if ($n !== null && $n >= 21001 && $n <= 21016) { return ['prontuario' => $prontuario, 'mensagem' => $msg]; } // 2901 a 2916 if ($n !== null && $n >= 2901 && $n <= 2916) { return ['prontuario' => $prontuario, 'mensagem' => $msg]; } // 31001 a 31016 if ($n !== null && $n >= 31001 && $n <= 31016) { return ['prontuario' => $prontuario, 'mensagem' => $msg]; } // 3901 a 3916 if ($n !== null && $n >= 3901 && $n <= 3916) { return ['prontuario' => $prontuario, 'mensagem' => $msg]; } // padrão return ['prontuario' => $prontuario, 'diaVisita' => getDiaVisitaPorBloco($alojamento)]; } function parseCsvToPplList(string $tmpPath): array { // tenta primeiro ; e depois , $raw = file_get_contents($tmpPath); if ($raw === false) { throw new RuntimeException('Falha ao ler arquivo enviado'); } $tryDelims = [';', ',']; $lastError = 'CSV inválido.'; foreach ($tryDelims as $delim) { $fh = fopen($tmpPath, 'r'); if (!$fh) continue; // detecta BOM no primeiro campo pelo cleanText $header = fgetcsv($fh, 0, $delim); if (!$header || !is_array($header)) { fclose($fh); $lastError = 'CSV sem cabeçalho.'; continue; } $headersNorm = array_map(fn($h) => normalizeHeader(cleanText($h)), $header); $idxPront = array_search('prontuario', $headersNorm, true); $idxAloj = array_search('alojamento', $headersNorm, true); if ($idxPront === false || $idxAloj === false) { fclose($fh); $lastError = 'CSV sem colunas obrigatórias: Prontuário e Alojamento.'; continue; } $list = []; while (($row = fgetcsv($fh, 0, $delim)) !== false) { if (!is_array($row)) continue; // ignora linhas vazias $allEmpty = true; foreach ($row as $cell) { if (trim((string)$cell) !== '') { $allEmpty = false; break; } } if ($allEmpty) continue; $pront = cleanText($row[$idxPront] ?? ''); $aloj = cleanText($row[$idxAloj] ?? ''); if ($pront === '' || $aloj === '') continue; $list[] = ['prontuario' => $pront, 'alojamento' => $aloj]; } fclose($fh); if (count($list) === 0) { $lastError = 'Nenhum registro válido foi encontrado no CSV.'; continue; } return $list; } throw new RuntimeException($lastError); }