From d42fd1eaba2816dc794349faf3bb0299466692c1 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Fri, 27 Mar 2026 14:56:27 +0100 Subject: [PATCH] Some more fixes to forum handling --- app/Providers/ForumServiceProvider.php | 27 +++++ app/Support/Forum/CategoryTreeBuilder.php | 107 ++++++++++++++++++ .../Support/Forum/CategoryTreeBuilderTest.php | 44 +++++++ 3 files changed, 178 insertions(+) create mode 100644 app/Support/Forum/CategoryTreeBuilder.php create mode 100644 tests/Unit/Support/Forum/CategoryTreeBuilderTest.php diff --git a/app/Providers/ForumServiceProvider.php b/app/Providers/ForumServiceProvider.php index f0f1fd097..76c7bf6d7 100644 --- a/app/Providers/ForumServiceProvider.php +++ b/app/Providers/ForumServiceProvider.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace App\Providers; +use App\Support\Forum\CategoryTreeBuilder; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; +use TeamTeaTime\Forum\Models\Category; use TeamTeaTime\Forum\Models\Post; class ForumServiceProvider extends ServiceProvider @@ -27,5 +30,29 @@ class ForumServiceProvider extends ServiceProvider Post::addGlobalScope('withAuthorRole', function (Builder $builder) { $builder->with(['author.role']); }); + + $categoryTreeBuilder = $this->app->make(CategoryTreeBuilder::class); + + View::composer('forum::category.index', function ($view) use ($categoryTreeBuilder): void { + $view->with('categories', $categoryTreeBuilder->buildAccessibleTo(request()->user())); + }); + + View::composer('forum::category.manage', function ($view) use ($categoryTreeBuilder): void { + $categories = Category::defaultOrder()->get(); + $tree = $categoryTreeBuilder->build($categories); + $categoryTreeBuilder->hideManageColumns($tree); + + $view->with('categories', $tree); + }); + + View::composer('forum::thread.show', function ($view) use ($categoryTreeBuilder): void { + $data = $view->getData(); + + if (! empty($data['categories'])) { + return; + } + + $view->with('categories', $categoryTreeBuilder->buildThreadDestinations()); + }); } } diff --git a/app/Support/Forum/CategoryTreeBuilder.php b/app/Support/Forum/CategoryTreeBuilder.php new file mode 100644 index 000000000..c05abd1e0 --- /dev/null +++ b/app/Support/Forum/CategoryTreeBuilder.php @@ -0,0 +1,107 @@ + $categories + * @return EloquentCollection + */ + public function build(EloquentCollection $categories): EloquentCollection + { + if ($categories->isEmpty()) { + return new EloquentCollection; + } + + $rootParentId = $this->resolveRootParentId($categories); + $categories->linkNodes(); + + /** @var EloquentCollection $tree */ + $tree = $categories + ->filter(static fn (Category $category): bool => $category->getParentId() === $rootParentId) + ->values(); + + return $this->removeParentRelationships($tree); + } + + /** + * @return EloquentCollection + */ + public function buildAccessibleTo(mixed $user): EloquentCollection + { + return $this->build(CategoryAccess::getFilteredTreeFor($user)); + } + + /** + * @return EloquentCollection + */ + public function buildThreadDestinations(): EloquentCollection + { + return $this->build(Category::query()->threadDestinations()->get()); + } + + /** + * @param EloquentCollection $categories + */ + public function hideManageColumns(EloquentCollection $categories): void + { + $categories->each(function (Category $category): void { + $category->makeHidden(['_lft', '_rgt', 'thread_count', 'post_count']); + + if ($category->relationLoaded('children') && $category->children->isNotEmpty()) { + /** @var EloquentCollection $children */ + $children = $category->children; + $this->hideManageColumns($children); + } + }); + } + + /** + * @param EloquentCollection $categories + */ + private function resolveRootParentId(EloquentCollection $categories): int|string|null + { + $rootParentId = null; + $lowestLeft = null; + + foreach ($categories as $category) { + $left = $category->getLft(); + + if ($lowestLeft === null || ($left !== null && $left < $lowestLeft)) { + $lowestLeft = $left; + $rootParentId = $category->getParentId(); + } + } + + return $rootParentId; + } + + /** + * @param EloquentCollection $categories + * @return EloquentCollection + */ + private function removeParentRelationships(EloquentCollection $categories): EloquentCollection + { + $categories->each(function (Category $category): void { + $category->setRelation('parent', null); + + if (! $category->relationLoaded('children')) { + return; + } + + /** @var EloquentCollection $children */ + $children = $category->children; + + $category->setRelation('children', $this->removeParentRelationships($children)); + }); + + return $categories; + } +} diff --git a/tests/Unit/Support/Forum/CategoryTreeBuilderTest.php b/tests/Unit/Support/Forum/CategoryTreeBuilderTest.php new file mode 100644 index 000000000..1db85e38e --- /dev/null +++ b/tests/Unit/Support/Forum/CategoryTreeBuilderTest.php @@ -0,0 +1,44 @@ +makeCategory(['id' => 1, 'title' => 'General', 'parent_id' => null, '_lft' => 1, '_rgt' => 4]); + $generalChild = $this->makeCategory(['id' => 4, 'title' => 'Introductions', 'parent_id' => 1, '_lft' => 2, '_rgt' => 3]); + $support = $this->makeCategory(['id' => 2, 'title' => 'Support', 'parent_id' => null, '_lft' => 5, '_rgt' => 6]); + $news = $this->makeCategory(['id' => 3, 'title' => 'News', 'parent_id' => null, '_lft' => 7, '_rgt' => 8]); + + $categories = $general->newCollection([$general, $generalChild, $support, $news]); + + $tree = $builder->build($categories); + + $this->assertCount(3, $tree); + $this->assertSame([1, 2, 3], $tree->pluck('id')->all()); + $this->assertCount(1, $tree->first()->children); + $this->assertSame(4, $tree->first()->children->first()->id); + $this->assertNull($tree->first()->getRelation('parent')); + } + + /** + * @param array $attributes + */ + private function makeCategory(array $attributes): Category + { + $category = new Category; + $category->setRawAttributes($attributes, true); + $category->exists = true; + + return $category; + } +}