filter_var($value, FILTER_VALIDATE_INT) !== false ? (int)$value : null, 'float' => filter_var($value, FILTER_VALIDATE_FLOAT) !== false ? (float)$value : null, 'email' => filter_var($value, FILTER_VALIDATE_EMAIL) ?: null, 'url' => filter_var($value, FILTER_VALIDATE_URL) ?: null, 'bool' => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), default => $value, // string — بدون htmlspecialchars (نتركه لـ PDO) }; } // ── ردود JSON موحدة ───────────────────────────────────────── function jsonSuccess(mixed $data = null, string $message = 'success', int $code = 200): never { http_response_code($code); // توحيد الأسلوب ليكون متوافقاً مع الكود القديم (وضع البيانات في message) $payload = ($data !== null && (!empty($data) || is_array($data))) ? $data : $message; echo json_encode(['status' => 'success', 'message' => $payload], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function jsonError(string $message, int $code = 400, mixed $extra = null): never { http_response_code($code); $response = ['status' => 'failure', 'message' => $message]; if ($extra !== null) $response['details'] = $extra; echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } // (للتوافق مع الكود القديم) function printSuccess(mixed $message = 'success'): void { echo json_encode(['status' => 'success', 'message' => $message], JSON_UNESCAPED_UNICODE); } function printFailure(mixed $message = 'failure'): void { echo json_encode(['status' => 'failure', 'message' => $message], JSON_UNESCAPED_UNICODE); } function result(int $count): void { if ($count > 0) { printSuccess(); } else { printFailure(); } } function sendEmail(string $from, string $to, string $title, string $body): void { $header = "From: $from\nCC: $from"; mail($to, $title, $body, $header); } // ── رفع صورة آمن ────────────────────────────────────────────── function uploadImageSecure( string $fileKey, string $targetDir, string $prefix = '', array $allowedMimes = ['image/jpeg', 'image/png', 'image/webp'] ): array { if (!isset($_FILES[$fileKey]) || $_FILES[$fileKey]['error'] !== UPLOAD_ERR_OK) { return ['success' => false, 'error' => 'File upload error']; } $file = $_FILES[$fileKey]; $maxSize = 5 * 1024 * 1024; // 5MB // حجم الملف if ($file['size'] > $maxSize) { return ['success' => false, 'error' => 'File too large (max 5MB)']; } // MIME validation حقيقي (ليس extension فقط) $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($file['tmp_name']); if (!in_array($mimeType, $allowedMimes, true)) { return ['success' => false, 'error' => "Invalid file type: $mimeType"]; } // اسم ملف آمن وعشوائي $ext = match ($mimeType) { 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', default => 'bin', }; $filename = ($prefix ? "{$prefix}_" : '') . bin2hex(random_bytes(8)) . ".$ext"; if (!is_dir($targetDir)) { mkdir($targetDir, 0750, true); } $targetPath = rtrim($targetDir, '/') . '/' . $filename; if (!move_uploaded_file($file['tmp_name'], $targetPath)) { return ['success' => false, 'error' => 'Failed to move uploaded file']; } return ['success' => true, 'filename' => $filename, 'path' => $targetPath]; } // ── تحميل ملف .env ─────────────────────────────────────────── function loadEnvironment(string $path): void { if (!file_exists($path)) { error_log("[ENV] File not found: $path"); return; } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (str_starts_with(trim($line), '#')) continue; if (!str_contains($line, '=')) continue; [$key, $value] = explode('=', $line, 2); $key = trim($key); $value = trim($value, " \t\n\r\0\x0B\"'"); if ($key && !getenv($key)) { putenv("$key=$value"); $_ENV[$key] = $value; } } } // ── Logging منظم ────────────────────────────────────────────── function securityLog(string $message, array $context = []): void { $logDir = __DIR__ . '/../logs'; if (!is_dir($logDir)) { @mkdir($logDir, 0777, true); } $entry = date('Y-m-d H:i:s') . ' [SECURITY] ' . $message; if ($context) $entry .= ' | ' . json_encode($context, JSON_UNESCAPED_UNICODE); @error_log($entry . PHP_EOL, 3, $logDir . '/security.log'); } function appLog(string $message, string $level = 'INFO'): void { $logDir = __DIR__ . '/../logs'; if (!is_dir($logDir)) { @mkdir($logDir, 0777, true); } $entry = date('Y-m-d H:i:s') . " [$level] " . $message; @error_log($entry . PHP_EOL, 3, $logDir . '/app.log'); } function uploadLog(string $message, string $level = 'INFO', array $context = []): void { $logDir = __DIR__ . '/../logs'; if (!is_dir($logDir)) { @mkdir($logDir, 0777, true); } if (!isset($context['ip'])) { $context['ip'] = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; } if (!isset($context['user_agent'])) { $context['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'; } if (isset($context['upload_error_code'])) { $errCode = $context['upload_error_code']; $context['upload_error_desc'] = match ($errCode) { UPLOAD_ERR_OK => 'UPLOAD_ERR_OK (0): No error, file uploaded successfully.', UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE (1): The uploaded file exceeds the upload_max_filesize directive in php.ini.', UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE (2): The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL (3): The uploaded file was only partially uploaded (common on weak/3G networks).', UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE (4): No file was uploaded.', UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR (6): Missing a temporary folder.', UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE (7): Failed to write file to disk.', UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION (8): A PHP extension stopped the file upload.', default => "Unknown upload error code: $errCode", }; } $entry = date('Y-m-d H:i:s') . " [$level] " . $message; if ($context) { $entry .= ' | ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } @error_log($entry . PHP_EOL, 3, $logDir . '/upload.log'); } function debugLog(string $message): void { appLog($message, 'DEBUG'); }