rootDir = $rootDir ?? ($_ENV['DOCUMENTS_ROOT'] ?? '/data/documents'); } public function sanitizeDocumentType(string $documentType): string { $documentType = trim($documentType); $documentType = preg_replace('/[^a-zA-Z0-9._-]+/', '_', $documentType); $documentType = trim($documentType, '._-'); if ($documentType === '') { throw new \RuntimeException('Invalid document_type'); } return $documentType; } public function ensureDirectory(int $companyId, int $userId, string $documentType): string { $documentType = $this->sanitizeDocumentType($documentType); $dir = rtrim($this->rootDir, '/'); $dir .= '/' . $companyId; $dir .= '/' . $userId; $dir .= '/' . $documentType; if (!is_dir($dir)) { if (!@mkdir($dir, 0775, true) && !is_dir($dir)) { throw new \RuntimeException('Failed to create documents directory'); } } return $dir; } public function buildStoredFilename(string $originalFilename, ?string $contentType = null): string { $ext = pathinfo($originalFilename, PATHINFO_EXTENSION); $ext = $ext ? strtolower($ext) : ''; if ($ext === '' && $contentType) { if (stripos($contentType, 'pdf') !== false) { $ext = 'pdf'; } } $name = bin2hex(random_bytes(16)); return $ext !== '' ? ($name . '.' . $ext) : $name; } public function writeFile(string $dir, string $filename, string $content): string { $dir = rtrim($dir, '/'); $path = $dir . '/' . $filename; $bytes = @file_put_contents($path, $content, LOCK_EX); if ($bytes === false) { throw new \RuntimeException('Failed to write file'); } return $path; } }