rootDir = $rootDir; return; } $documentsRoot = (string)($_ENV['DOCUMENTS_ROOT'] ?? ''); if (trim($documentsRoot) !== '') { $this->rootDir = $documentsRoot; return; } $appRootDir = (string)($_ENV['ROOT_DIR'] ?? ''); if (trim($appRootDir) !== '') { $this->rootDir = rtrim($appRootDir, '/') . '/data/documents'; return; } $this->rootDir = dirname(__DIR__) . '/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)) { $lastError = error_get_last(); $root = rtrim($this->rootDir, '/'); $rootParent = dirname($root); $debug = [ 'root_dir' => $root, 'root_dir_exists' => is_dir($root), 'root_dir_writable' => is_writable($root), 'root_parent' => $rootParent, 'root_parent_exists' => is_dir($rootParent), 'root_parent_writable' => is_writable($rootParent), 'target_dir' => $dir, 'target_parent' => dirname($dir), 'target_parent_exists' => is_dir(dirname($dir)), 'target_parent_writable' => is_writable(dirname($dir)), 'php_user' => get_current_user(), 'euid' => function_exists('posix_geteuid') ? posix_geteuid() : null, 'last_error' => $lastError, ]; throw new \RuntimeException('Failed to create documents directory: ' . json_encode($debug, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } } 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) { $lastError = error_get_last(); $debug = [ 'path' => $path, 'dir_exists' => is_dir($dir), 'dir_writable' => is_writable($dir), 'last_error' => $lastError, ]; throw new \RuntimeException('Failed to write file: ' . json_encode($debug, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } return $path; } }