HASH_COST]); } /** * Verify password */ function verifyPassword($password, $hash) { return password_verify($password, $hash); } /** * Sanitize input */ function sanitize($input) { if (is_array($input)) { return array_map('sanitize', $input); } return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } /** * Format currency */ function formatCurrency($amount) { return TJJ_CURRENCY_SYMBOL . number_format((float)$amount, 2); } /** * Format date */ function formatDate($date, $format = 'M j, Y') { return date($format, strtotime($date)); } /** * Format datetime */ function formatDateTime($date, $format = 'M j, Y g:i A') { return date($format, strtotime($date)); } /** * JSON response helper */ function jsonResponse($data, $statusCode = 200) { http_response_code($statusCode); header('Content-Type: application/json'); echo json_encode($data); exit; } /** * Redirect helper */ function redirect($url) { header("Location: $url"); exit; } /** * Get current URL */ function currentUrl() { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; return $protocol . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } /** * Check if request is AJAX */ function isAjax() { return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; } /** * Get client IP address */ function getClientIp() { $ip = $_SERVER['REMOTE_ADDR']; if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } return trim($ip); } /** * Generate CSRF token */ function generateCsrfToken() { if (empty($_SESSION[CSRF_TOKEN_NAME])) { $_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32)); } return $_SESSION[CSRF_TOKEN_NAME]; } /** * Verify CSRF token */ function verifyCsrfToken($token) { return isset($_SESSION[CSRF_TOKEN_NAME]) && hash_equals($_SESSION[CSRF_TOKEN_NAME], $token); } /** * Get setting value */ function getSetting($key, $default = null) { $result = db()->fetch( "SELECT setting_value FROM settings WHERE setting_key = :key", ['key' => $key] ); if ($result) { return json_decode($result['setting_value'], true) ?? $result['setting_value']; } return $default; } /** * Update setting value */ function setSetting($key, $value) { $jsonValue = json_encode($value); $existing = db()->fetch( "SELECT id FROM settings WHERE setting_key = :key", ['key' => $key] ); if ($existing) { db()->update('settings', ['setting_value' => $jsonValue], 'setting_key = :key', ['key' => $key]); } else { db()->insert('settings', ['setting_key' => $key, 'setting_value' => $jsonValue]); } } /** * Flash message helpers */ function setFlash($type, $message) { $_SESSION['flash'][$type] = $message; } function getFlash($type) { if (isset($_SESSION['flash'][$type])) { $message = $_SESSION['flash'][$type]; unset($_SESSION['flash'][$type]); return $message; } return null; } function hasFlash($type) { return isset($_SESSION['flash'][$type]); } /** * Pagination helper */ function paginate($totalItems, $currentPage, $perPage = ITEMS_PER_PAGE) { $totalPages = ceil($totalItems / $perPage); $currentPage = max(1, min($currentPage, $totalPages)); $offset = ($currentPage - 1) * $perPage; return [ 'total_items' => $totalItems, 'total_pages' => $totalPages, 'current_page' => $currentPage, 'per_page' => $perPage, 'offset' => $offset, 'has_prev' => $currentPage > 1, 'has_next' => $currentPage < $totalPages ]; } /** * Render pagination HTML */ function renderPagination($pagination, $baseUrl) { if ($pagination['total_pages'] <= 1) return ''; $cur = $pagination['current_page']; $total = $pagination['total_pages']; // baseUrl may already contain query params; append page with & $sep = strpos($baseUrl, '?') !== false ? '&' : '?'; $url = fn($p) => $baseUrl . $sep . 'page=' . $p; $btn = fn($href, $label, $active = false, $disabled = false) => '' . $label . ''; $html = '