Files
2026-04-28 21:14:25 +03:00

189 lines
5.3 KiB
PHP

<?php
namespace App\Services\DocumentManager;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use FilesystemIterator;
class FolderCatalog
{
/**
* Cached flattened catalog.
*/
protected ?Collection $flattened = null;
/**
* Raw catalog definition pulled from configuration.
*/
protected array $catalog;
public function __construct(?array $catalog = null)
{
$this->catalog = $catalog ?? (array) config('document-folders.catalog', []);
}
/**
* Get the raw catalog structure as defined in the configuration file.
*/
public function all(): array
{
return $this->catalog;
}
/**
* Get a flattened collection containing each folder entry with metadata.
*/
public function flattened(): Collection
{
if ($this->flattened !== null) {
return $this->flattened;
}
$items = $this->flattenNodes($this->catalog);
return $this->flattened = collect($items)
->map(function (array $item) {
$path = trim($item['path'] ?? '', '/');
$number = Arr::get($item, 'number');
$name = Arr::get($item, 'name');
return [
'number' => $number,
'name' => $name,
'path' => $path,
'default_visible' => Arr::get($item, 'default_visible', true),
'is_system' => Arr::get($item, 'is_system', false),
'level' => Arr::get($item, 'level', 0),
'display' => trim(sprintf('%s %s', $number, $name)),
];
})
->filter(fn ($item) => $item['path'] !== '')
->values();
}
/**
* Return a collection of directory paths relative to storage/documents.
*/
public function paths(): Collection
{
return $this->flattened()
->pluck('path')
->map(fn ($path) => trim($path, '/'))
->unique()
->values();
}
/**
* Create missing directories under the provided documents root path.
*
* @return array{created: array, existing: array}
*/
public function sync(string $rootPath, bool $dryRun = false): array
{
$rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR);
$created = [];
$existing = [];
foreach ($this->paths() as $relativePath) {
$fullPath = $rootPath . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relativePath);
if (File::isDirectory($fullPath)) {
$existing[] = $relativePath;
continue;
}
if (! $dryRun) {
File::ensureDirectoryExists($fullPath);
}
$created[] = $relativePath;
}
return compact('created', 'existing');
}
/**
* Detect directories that exist on disk but are not part of the catalog.
*/
public function detectExtra(string $rootPath): Collection
{
$rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR);
if (! File::isDirectory($rootPath)) {
return collect();
}
$defined = $this->paths();
$actual = collect($this->scanDirectories($rootPath));
return $actual->diff($defined)->values();
}
/**
* Recursively flatten catalog definition.
*/
protected function flattenNodes(array $nodes, int $level = 0): array
{
$items = [];
foreach ($nodes as $node) {
if (! is_array($node)) {
continue;
}
$item = $node;
$item['level'] = $level;
$items[] = $item;
if (! empty($node['children']) && is_array($node['children'])) {
$items = array_merge($items, $this->flattenNodes($node['children'], $level + 1));
}
}
return $items;
}
/**
* Collect relative directory paths recursively from the given root.
*/
protected function scanDirectories(string $rootPath): array
{
$rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR);
$directories = [];
if (! File::isDirectory($rootPath)) {
return $directories;
}
// Include direct children first (top-level ordering)
foreach (File::directories($rootPath) as $directory) {
$relative = trim(Str::after($directory, $rootPath . DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR);
if ($relative !== '') {
$directories[] = $relative;
}
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootPath, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $path => $info) {
if ($info->isDir()) {
$relative = trim(Str::after($path, $rootPath . DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR);
if ($relative !== '') {
$directories[] = $relative;
}
}
}
return array_values(array_unique($directories));
}
}