ldc-groupe/app/Http/Controllers/Front/PostsController.php
2026-02-13 11:17:43 +01:00

332 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Front;
use Carbon\Carbon;
use App\Models\News;
use App\Models\NewsTag;
use App\Enums\YearsEnum;
use App\Models\Document;
use App\Enums\OrdersEnum;
use App\Models\DocumentTag;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use App\Http\Requests\Front\PostsRequest;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class PostsController extends Controller
{
/** @var string */
protected const DOCUMENTS = "documents";
/** @var string */
protected const NEWS = "news";
/**
* Extract unique years from date field in a collection.
*
* @param \Illuminate\Support\Collection $model
* @param string $field
* @return \Illuminate\Support\Collection
*/
private function getUniqueYearsFromCollection(Collection $model, string $field): \Illuminate\Support\Collection
{
return $model->pluck($field)
->map(fn($date) => Carbon::parse($date)->year)
->unique()
->values();
}
/**
* Filter input collection to only keep items whose value exists in a reference collection.
*
* @param \Illuminate\Support\Collection $array1
* @param \Illuminate\Support\Collection $array2
* @param string|null $field
* @return array
*/
private function getMatchingValues(Collection $array1, Collection $array2, ?string $field = null): array
{
return $array2
->filter(fn ($item) => $array1->contains($field ? $item[$field] : $item))
->values()
->toArray();
}
/**
* Build the common filters configuration for front-end listing pages.
* If the type is "documents" the "order" filter is included.
*
* @template TTag of \App\Models\DocumentTag|\App\Models\NewsTag
* @template TModel of \App\Models\Document|\App\Models\News
*
* @param string $type
* @param string $category
* @param \Illuminate\Database\Eloquent\Collection<int, TTag> $publishedTags
* @param \Illuminate\Database\Eloquent\Collection<int, TModel> $model
* @return array
*/
private function getFilters(string $type, string $category, Collection $publishedTags, Collection $model): array
{
$filters = [];
$tags = collect($publishedTags)
->map(fn($tag) => [
'title' => $tag->name,
'tag' => $tag->slug,
'category' => $category,
])
->values();
$enumYears = collect(YearsEnum::toArray())
->map(fn($year) => YearsEnum::make((int) $year->value))
->map(fn($year) => [
'title' => trans($year->label()),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $type === self::NEWS ? explode('/', $year->label())[1] : $year->value,
'category' => $category,
])
->sortDesc()
->values();
$orders = collect(OrdersEnum::toArray())
->map(fn($order) => OrdersEnum::make((int) $order->value))
->filter(fn($orderLocal) => $orderLocal->ordersWithLocal())
->map(fn($orderLocal) => [
'title' => ucfirst(trans($orderLocal->label())),
'tag' => Str::slug($orderLocal->name),
'category' => $category,
])
->values();
// Sort displayed years.
$years = $this->getMatchingValues(
$this->getUniqueYearsFromCollection($model, 'published_at'),
$enumYears,
'value'
);
if ($type === self::DOCUMENTS) {
$filters[] = [
'title' => ucfirst(__('front.news.order')),
'name' => 'order',
'fields' => $orders,
];
}
$filters[] = [
'title' => $type === self::DOCUMENTS
? ucfirst(__('front.news.document'))
: ucfirst(__('front.news.theme')),
'name' => 'tags',
'fields' => $tags,
];
$filters[] = [
'title' => $type === self::DOCUMENTS
? ucfirst(__('front.news.exercise'))
: ucfirst(__('front.news.year')),
'name' => 'years',
'fields' => $years,
];
return $filters;
}
/**
* Returns news in JSON format filtered by category and language.
* Optionally, the results can be further filtered by tags, and years.
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadNews(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$validatedData = $request->validated();
$perPage = 9;
$selectedFilters = [];
/** @var string $slugCategory */
$slugCategory = $validatedData['category'];
app()->setLocale($validatedData['locale']);
$publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($slugCategory);
$newsModel = News::getPublishedNewsByCategoryInCurrentLocale($slugCategory);
$requestedTags = collect($validatedData['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $publishedNewsTags->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $slugCategory,
])
->values();
if ($selectedTags->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($newsModel) use ($selectedTags) {
return $newsModel->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($validatedData['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => explode('/', $year->label())[1],
'category' => $slugCategory,
])
->values();
if ($selectedYears->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($news) use ($selectedYears) {
return $selectedYears->contains('value', $news->published_at->year);
});
$selectedFilters['years'] = $selectedYears;
}
$newsModel = $newsModel->sortByDesc('published_at');
$filters = $this->getFilters('news', $slugCategory, $publishedNewsTags, $newsModel);
// Custom pagination.
$newsModel = CollectionHelper::paginate($newsModel, $perPage);
return response()->json([
'pagination' => [
'total' => $newsModel->total(),
'nbItems' => count($newsModel->items()),
'currentPage' => $newsModel->currentPage(),
'lastPage' => $newsModel->lastPage(),
'hasMore' => $newsModel->currentPage() < $newsModel->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', [
'selectedFilters' => $selectedFilters,
])->render(),
'news' => view('components.front.posts.news', ['newsList' => $newsModel->items()])->render()
]);
}
/**
* Returns documents in JSON format filtered by category and language.
* Optionally, the results can be further filtered by order, tags, and years.
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadDocuments(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$validatedData = $request->validated();
$perPage = 3;
$selectedFilters = [];
/** @var string $slugCategory */
$slugCategory = $validatedData['category'];
app()->setLocale($validatedData['locale']);
$documentTagModels = DocumentTag::getTagsByCategoryInCurrentLocale($slugCategory);
$documentModels = Document::getPublishedDocumentsByCategoryInCurrentLocale($slugCategory);
$requestedTags = collect($validatedData['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $documentTagModels
->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $slugCategory,
])
->values();
if ($selectedTags->isNotEmpty()) {
$documentModels = $documentModels->filter(function ($documentModel) use ($selectedTags) {
return $documentModel->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($validatedData['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $year->value,
'category' => $slugCategory,
])
->values();
if ($selectedYears->isNotEmpty()) {
$documentModels = $documentModels->filter(function ($documentModel) use ($selectedYears) {
return $selectedYears->contains('value', $documentModel->exercice);
});
$selectedFilters['years'] = $selectedYears;
}
$selectedOrder = collect($validatedData['filters']['order'] ?? '')
->map(fn (string $label) => OrdersEnum::makeFromLabel($label))
->filter()
->map(fn($order) => [
'title' => trans($order->label()),
'tag' => $order->name(),
'category' => $slugCategory,
])
->values();
if ($selectedOrder->isNotEmpty()) {
$firstTag = $selectedOrder->map(fn($order) => $order['tag'])->first();
$documentModels = $firstTag === 'desc'
? $documentModels->sortByDesc('published_at')
: $documentModels->sortBy('published_at');
$selectedFilters['order'] = $selectedOrder->all();
} else {
$documentModels = $documentModels->sortByDesc('published_at');
}
$filters = $this->getFilters('documents', $slugCategory, $documentTagModels, $documentModels);
// Custom pagination.
$documentModels = CollectionHelper::paginate($documentModels, $perPage);
return response()->json([
'pagination' => [
'total' => $documentModels->total(),
'nbItems' => count($documentModels->items()),
'currentPage' => $documentModels->currentPage(),
'lastPage' => $documentModels->lastPage(),
'hasMore' => $documentModels->currentPage() < $documentModels->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', [
'selectedFilters' => $selectedFilters,
])->render(),
'news' => view('components.front.posts.documents', [
'documentsList' => $documentModels->items(),
])->render()
]);
}
}