ensureWritableDirectory($basePath, 'Additional post-processing temp path'); return $basePath; } /** * Create a per-release temp folder and return its path. */ public function createReleaseTempFolder(string $mainTmpPath, string $guid): string { $this->assertWritableDirectory(rtrim($mainTmpPath, '/\\'), 'Additional post-processing temp path'); $tmpPath = rtrim($mainTmpPath, '/\\').'/'.$guid.'/'; $this->ensureWritableDirectory($tmpPath, 'Release temp path'); return $tmpPath; } /** * Delete files recursively. If $preserveRoot is true, only clear contents of $path. */ public function clearDirectory(string $path, bool $preserveRoot = true): void { if ($path === '' || ! File::exists($path)) { return; } if (File::isDirectory($path)) { // Delete all files recursively foreach (File::allFiles($path) as $file) { File::delete($file->getPathname()); } // Delete sub-directories foreach (File::directories($path) as $dir) { File::deleteDirectory($dir); } if (! $preserveRoot) { File::deleteDirectory($path); } } elseif (File::isFile($path)) { File::delete($path); } } /** * List files under a directory. * If $pattern is provided, return an array of preg_match($pattern, $relativePath, $matches) arrays, * where $matches[0] is the ABSOLUTE path for convenience (remaining capture groups preserved if present). * Otherwise return an array of SplFileInfo. * * @return array|\SplFileInfo> */ public function listFiles(string $path, string $pattern = ''): array { try { $files = File::allFiles($path); } catch (Throwable $e) { throw new RuntimeException('ERROR: Could not open temp dir: '.$e->getMessage()); } if ($pattern !== '') { $filtered = []; $base = rtrim($path, '/\\'); foreach ($files as $file) { $relative = $file->getRelativePathname(); if (preg_match($pattern, $relative, $matches)) { // Overwrite full match with absolute path to make downstream file ops straightforward $matches[0] = $base.'/'.$relative; $filtered[] = $matches; } } return $filtered; } return $files; } private function ensureWritableDirectory(string $path, string $label): void { if (File::isDirectory($path)) { $this->repairDirectoryIfPossible($path); } else { try { if (! File::makeDirectory($path, 0777, true, true) && ! File::isDirectory($path)) { // @phpstan-ignore booleanNot.alwaysTrue throw new RuntimeException('mkdir returned false'); } } catch (Throwable $e) { throw new RuntimeException(sprintf( '%s "%s" could not be created: %s', $label, $path, $e->getMessage() ), 0, $e); } } $this->assertWritableDirectory($path, $label); } private function repairDirectoryIfPossible(string $path): void { if (is_writable($path)) { return; } @chmod($path, 0777); clearstatcache(true, $path); if (is_writable($path)) { return; } $normalizedPath = rtrim($path, '/\\'); $parent = dirname($normalizedPath); if (! File::isDirectory($parent) || ! is_writable($parent)) { return; } File::deleteDirectory($normalizedPath); if (! File::isDirectory($normalizedPath)) { File::makeDirectory($normalizedPath, 0777, true, true); } } private function assertWritableDirectory(string $path, string $label): void { if (! File::isDirectory($path)) { throw new RuntimeException(sprintf('%s "%s" is not a directory', $label, $path)); } if (! is_writable($path)) { throw new RuntimeException(sprintf( '%s "%s" is not writable by the PHP/tmux user. Check TEMP_UNRAR_PATH ownership and permissions.', $label, $path )); } } }