isDir() && in_array($item->getBasename(), ExtensionPendingHelper::EXCLUDED_DIRECTORIES, true)) { continue; } $target = $dest.DIRECTORY_SEPARATOR.$item->getBasename(); $relativePath = ltrim(str_replace($basePath, '', $item->getPathname()), '/\\'); if ($item->isDir()) { self::copyDirectoryWithProgress($item->getPathname(), $target, $basePath, $onProgress); } else { $onProgress?->__invoke(null, $relativePath); FilePermissionHelper::copyFile($item->getPathname(), $target); } } } /** * 백업으로부터 확장 디렉토리를 복원합니다. * * ExtensionPendingHelper::copyToActive()의 원자적 교체 패턴을 사용합니다. * * @param string $type 확장 타입 ('modules', 'plugins', 'templates') * @param string $identifier 확장 식별자 * @param string $backupPath 백업 경로 * @param \Closure|null $onProgress 진행 콜백 (?string $step, string $message) * * @throws \RuntimeException 백업 디렉토리가 없을 때 * * @see ExtensionPendingHelper::copyToActive() 원자적 디렉토리 교체 로직 */ public static function restoreFromBackup(string $type, string $identifier, string $backupPath, ?\Closure $onProgress = null): void { if (! File::isDirectory($backupPath)) { throw new \RuntimeException( "Backup directory does not exist: {$backupPath}" ); } $targetPath = base_path("{$type}/{$identifier}"); // 원자적 복원: ExtensionPendingHelper의 안전한 교체 로직 사용 ExtensionPendingHelper::copyToActive($backupPath, $targetPath, $onProgress); } /** * 백업 디렉토리를 삭제합니다. * * @param string $backupPath 백업 경로 */ public static function deleteBackup(string $backupPath): void { if (File::isDirectory($backupPath)) { File::deleteDirectory($backupPath); } } }