['label' => 'Half Day (4 hrs)', 'amount' => 99.00, 'days' => 0], 'full-day' => ['label' => 'Full Day (8 hrs)', 'amount' => 169.00, 'days' => 0], 'weekend' => ['label' => 'Weekend (48 hrs)', 'amount' => 299.00, 'days' => 1], ]); function db(): PDO { static $pdo; if (!$pdo) { $pdo = new PDO( 'mysql:host=' . PARKER_DB_HOST . ';dbname=' . PARKER_DB_NAME . ';charset=utf8mb4', PARKER_DB_USER, PARKER_DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] ); } return $pdo; } function squareApi(string $method, string $path, array $body = []): array { $ch = curl_init('https://connect.squareup.com/v2' . $path); $headers = [ 'Authorization: Bearer ' . SQUARE_ACCESS_TOKEN, 'Content-Type: application/json', 'Square-Version: ' . SQUARE_VERSION, ]; $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30]; if ($method === 'POST') { $opts[CURLOPT_POST] = true; $opts[CURLOPT_POSTFIELDS] = $body ? json_encode($body) : '{}'; } curl_setopt_array($ch, $opts); $resp = curl_exec($ch); curl_close($ch); return json_decode($resp ?: '{}', true); } function generateRef(): string { // 4 random bytes -> 8 hex chars; 'PSR-' + 8 chars = 12, matches booking_ref varchar(12). return 'PSR-' . strtoupper(bin2hex(random_bytes(4))); } // Shared by view-doc.php and admin/view-doc.php so the admin-token check and // path-traversal/mime guards live in exactly one place. function verifyAdminToken(string $token): bool { if (!preg_match('/^[a-f0-9]{64}$/', $token)) return false; $stmt = db()->prepare("SELECT token FROM admin_tokens WHERE token=? AND expires_at > NOW()"); $stmt->execute([$token]); return (bool)$stmt->fetch(); } const ALLOWED_DOC_MIMES = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf']; function resolveUploadPath(string $root, string $relativePath): ?string { $base = realpath($root . '/uploads'); $path = realpath($root . '/' . $relativePath); if (!$path || !$base || !str_starts_with($path, $base . DIRECTORY_SEPARATOR)) { return null; } return $path; } function detectAllowedMime(string $path): ?string { $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($path); return isset(ALLOWED_DOC_MIMES[$mime]) ? $mime : null; } function sendEmail(string $to, string $toName, string $subject, string $html): bool { $apiKey = defined('CYBERMAIL_API_KEY') ? CYBERMAIL_API_KEY : ''; if (!$apiKey || strpos($apiKey, 'YOUR_KEY') !== false) return false; $payload = json_encode([ 'from' => MAIL_FROM, 'to' => $to, 'subject' => $subject, 'html' => $html, ]); $ch = curl_init('https://platform.cyberpersons.com/email/v1/send'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_TIMEOUT => 15, ]); $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $code === 202; }