ldc-legaulois/app/Http/Controllers/Fo/ProductController.php
2025-06-11 09:59:07 +02:00

581 lines
25 KiB
PHP

<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\CollectionHelper;
use App\Models\Meat;
use App\Models\Product;
use App\Models\Range;
use App\Models\Recipe;
use App\Models\StaticPage;
use App\Models\Submeat;
use App\Models\Subrange;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
class ProductController extends Controller
{
/**
* Use for pagination,
* number of product models per page.
*
* @var integer
*/
private $productModelsPerPage = 8;
/**
* Get products by a specific range into a json response.
*
* @param \Illuminate\Pagination\LengthAwarePaginator|null $productModelsFiltered
* @param \Illuminate\Support\Collection<\App\Models\Subrange>|null $subrangeModelsFiltered
* @param \Illuminate\Support\Collection<\App\Models\Submeat>|null $submeatModelsFiltered
* @return \Illuminate\Contracts\View\View
*/
public function index(
LengthAwarePaginator|null $productModelsFiltered = null,
Collection|null $subrangeModelsFiltered = null,
Collection|null $submeatModelsFiltered = null,
): \Illuminate\Contracts\View\View {
/** @var \Illuminate\Pagination\LengthAwarePaginator $productModels */
$productModels = $productModelsFiltered ?? Product::query()
->where('published', true)
->orderBy('slug')
->paginate($this->productModelsPerPage);
$productDiscoverModels = Product::getPublishedDiscovered();
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::products);
$rangeModels = Range::getPublishedOrderedWithSubranges();
$subrangeModels = $subrangeModelsFiltered ?? Subrange::getPublishedOrdered();
$meatModels = Meat::getPublishedOrderedWithSubmeats();
$submeatModels = $submeatModelsFiltered ?? Submeat::getPublishedOrdered();
return view('fo.pages.products.index', [
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'productDiscoverModels' => $productDiscoverModels,
'staticPageModel' => $staticPageModel,
'rangeModels' => $rangeModels,
'subrangeModels' => $subrangeModels,
'meatModels' => $meatModels,
'submeatModels' => $submeatModels,
'searchPropositions' => $this->getProductModelsTitles(),
]);
}
/**
* Get the view of a specific product.
*
* @param string $slug
* @return \Illuminate\Contracts\View\View
*/
public function show(string $slug): \Illuminate\Contracts\View\View
{
/** @var \App\Models\Product|null $productModel Current product model */
$productModel = Product::query()->where('slug', $slug)->first();
if (is_null($productModel)) {
abort(404);
}
$productModelSibling = $this->getSiblingModels($productModel);
$staticPageModel = StaticPage::getCachedStaticPage(StaticPageType::product);
/** @var \App\Models\Recipe|null $recipeModel */
$recipeModel = $productModel->recipes->isNotEmpty()
? $productModel->recipes()->with(['products', 'consumptionMoment'])->first()
: Recipe::query()->whereHas('products', function (Builder $q) use ($productModel) {
$q->when($productModel->subrange, function (Builder $q) use ($productModel) {
$q->whereHas('subrange', function (Builder $q) use ($productModel) {
$q->where('id', $productModel->subrange->getKey());
});
})->when($productModel->range, function (Builder $q) use ($productModel) {
$q->whereHas('range', function (Builder $q) use ($productModel) {
$q->where('id', $productModel->range->getKey());
});
});
})->with(['products', 'consumptionMoment'])->first();
/** @var \App\Models\Video $videoModel */
$videoModel = $productModel->videos->sortByDesc('created_at')->first();
$nutritionnalValueModel = $productModel->getNutritionnalValue();
return view('fo.pages.products.show', [
'productModel' => $productModel,
'nutritionnalValueModel' => $nutritionnalValueModel,
'prevProductModel' => $productModelSibling[0],
'nextProductModel' => $productModelSibling[1],
'productModels' => $this->getProductsFiltered($productModel),
'recipeModels' => $this->getRecipesFiltered($productModel),
'recipeModel' => $recipeModel,
'videoModel' => $videoModel,
'staticPageModel' => $staticPageModel,
]);
}
/**
* Get product models titles for search input propositions.
*
* @param \App\Models\Product $productModel
* @return array
*/
public function getSiblingModels(Product $productModel): array
{
$filter = Cache::get('productsFilter');
$cleanedFilter = $this->cleanSubSubFilter($filter);
/** @var string $slug */
$slug = $filter
? ((is_null($productModel->{$filter})
? $productModel->{"sub" . $this->cleanSubSubFilter($filter)}
->{$this->cleanSubSubFilter($filter)} ?? null
: $productModel->{$filter})->slug ?? "")
: "";
// FIXME: the previous element are added on 30/10/24 to avoid error when productFilters was set on "submeat"
// for example and user access to product which not have submeat relation -> "Attempt to read property 'sub***' on null"
// this fix will just avoid the error, but in this case, product.show have no previous or next product.
/** @var \Illuminate\Support\Collection $productModelsList */
$productModelsList = Product::query()
->with('range', 'subrange', 'meat', 'submeat')
->when(in_array($filter, ['range', 'meat']), function (Builder $q) use ($slug, $filter) {
$q->where('published', true)
->whereHas($filter, function (Builder $q) use ($slug) {
$q->where('published', true)->where('slug', $slug);
})
->orWhereHas('sub' . $filter, function (Builder $q) use ($slug, $filter) {
$q->where('published', true)
->whereHas($filter, function (Builder $q) use ($slug) {
$q->where('published', true)->where('slug', $slug);
});
});
})
->when(in_array($filter, ['subrange', 'submeat']), function (Builder $q) use ($slug, $filter) {
$q->where('published', true)
->whereHas($filter, function (Builder $q) use ($slug) {
$q->where('published', true)->where('slug', $slug);
});
})
->where('published', true)
->orderBy('slug')
->get();
$collectionIndex = $productModelsList->search(function ($productModelItem, $key) use ($productModel) {
if ($productModelItem->getKey() === $productModel->getKey()) {
return $key;
};
});
$productModelSibling[0] = (is_null($productModelsList->get($collectionIndex - 1)))
? $productModelsList->last()
: $productModelsList->get($collectionIndex - 1);
$productModelSibling[1] = (is_null($productModelsList->get($collectionIndex + 1)))
? $productModelsList->first()
: $productModelsList->get($collectionIndex + 1);
return $productModelSibling;
}
/**
* Get product models titles for search input propositions.
*
* @param string|null $search
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public function getProductModelsTitles(string|null $search = null): \Illuminate\Pagination\LengthAwarePaginator
{
return $this->getModelsTitles(Product::query(), 'label', $search);
}
/**
* Get a list of words for the search input propositions.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getWordsList(Request $request): \Illuminate\Http\JsonResponse
{
return response()->json($this->getProductModelsTitles($request->input('search')));
}
/**
* Get products by specific words,
* the response can be a json or a view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
*/
public function getProductsByWords(Request $request): \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $productModels */
$productModels = Product::query()
->with(['range', 'subrange', 'meat', 'submeat'])
->where('published', true)
->orderBy('slug');
/** @var string $search Search field */
$search = $request->input('words');
if ($search) {
$this->searchQuery(
$productModels,
str_replace(",", " ", $request->input('words')),
null,
'label',
);
}
$productModels = $productModels->paginate($this->productModelsPerPage);
if ($request->ajax()) {
return response()->json([
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'children' => [],
]);
}
return $this->index($productModels, collect());
}
/**
* Get products by a specific meat,
* the response can be a json or a view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
*/
public function getProductsByRange(
Request $request
): \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View {
Cache::put('productsFilter', 'range');
$rangeModel = Range::query()
->where('slug', $request->model_slug)
->where('published', true)
->first();
$subrangeModels = Subrange::query()
->when(!is_null($rangeModel), function (Builder $q) use ($rangeModel) {
$q->where('range_id', $rangeModel->getKey());
})
->where('published', true)
->orderBy('order')
->get();
$productModels = Product::query()
->with(['range', 'subrange', 'meat', 'submeat'])
->where('published', true)
->when(!is_null($rangeModel), function (Builder $q) use ($rangeModel, $request) {
$q->whereHas('range', function (Builder $q) use ($rangeModel) {
$q->where('published', true)->where('id', $rangeModel->getKey());
})->orWhereHas('subrange', function (Builder $q) use ($rangeModel, $request) {
if ($request->child_slug) {
$q->where('published', true)->where("slug", $request->child_slug);
} else {
$q->where('published', true)->whereIn('id', $rangeModel->subranges->pluck('id')->toArray());
}
});
})
->where('published', true)
->orderBy('slug')
->paginate($this->productModelsPerPage);
if ($request->ajax()) {
return response()->json([
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'children' => $subrangeModels,
'productModels' => $productModels,
]);
}
return $this->index($productModels, $subrangeModels);
}
/**
* Get products by a specific submeat,
* the response can be a json or a view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
*/
public function getProductsBySubrange(
Request $request
): \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View {
Cache::put('productsFilter', 'subrange');
$rangeModel = Range::query()
->where('slug', $request->model_slug)
->where('published', true)
->first();
$subrangeModel = Subrange::query()
->when(!is_null($rangeModel), function (Builder $q) use ($rangeModel) {
$q->where('range_id', $rangeModel->getKey());
})
->where('slug', $request->child_slug)
->where('published', true)
->first();
$subrangeModels = Subrange::query()
->when(!is_null($rangeModel), function (Builder $q) use ($rangeModel) {
$q->where('range_id', $rangeModel->getKey());
})
->where('published', true)
->orderBy('order')
->get();
$productModels = Product::query()
->with(['range', 'subrange', 'meat', 'submeat'])
->where('published', true)
->when(!is_null($subrangeModel), function (Builder $q) use ($subrangeModel) {
$q->whereHas('subrange', function (Builder $q) use ($subrangeModel) {
$q->where('published', true)->where('id', $subrangeModel->getKey());
});
})
->orderBy('slug')
->paginate($this->productModelsPerPage);
if ($request->ajax()) {
return response()->json([
'productModels' => $productModels,
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'children' => $subrangeModels,
]);
}
return $this->index($productModels, $subrangeModels);
}
/**
* Get products by a specific meat,
* the response can be a json or a view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
*/
public function getProductsByMeat(
Request $request
): \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View {
Cache::put('productsFilter', 'meat');
$meatModel = Meat::query()
->where('slug', $request->model_slug)
->where('published', true)
->first();
$submeatModels = Submeat::query()
->when(!is_null($meatModel), function (Builder $q) use ($meatModel) {
$q->where('meat_id', $meatModel->getKey());
})
->where('published', true)
->orderBy('order')
->get();
$productModels = Product::query()
->with(['range', 'subrange', 'meat', 'submeat'])
->when(!is_null($meatModel), function (Builder $q) use ($meatModel) {
$q->whereHas('meat', function (Builder $q) use ($meatModel) {
$q->where('published', true)->where('id', $meatModel->getKey());
})->orWhereHas('submeat', function (Builder $q) use ($meatModel) {
$q->where('published', true)->whereIn('id', $meatModel->submeats->pluck('id')->toArray());
});
})
->where('published', true)
->orderBy('slug')
->paginate($this->productModelsPerPage);
if ($request->ajax()) {
return response()->json([
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'children' => $submeatModels,
]);
}
return $this->index($productModels, null, $submeatModels);
}
/**
* Get products by a specific submeat,
* the response can be a json or a view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View
*/
public function getProductsBySubmeat(
Request $request
): \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View {
Cache::put('productsFilter', 'submeat');
$meatModel = Meat::query()
->where('slug', $request->model_slug)
->where('published', true)
->first();
$submeatModel = Submeat::query()
->when(!is_null($meatModel), function (Builder $q) use ($meatModel) {
$q->where('meat_id', $meatModel->getKey());
})
->where('slug', $request->child_slug)
->where('published', true)
->first();
$submeatModels = Submeat::query()
->when(!is_null($meatModel), function (Builder $q) use ($meatModel) {
$q->where('meat_id', $meatModel->getKey());
})
->where('published', true)
->orderBy('order')
->get();
$productModels = Product::query()
->with(['range', 'subrange', 'meat', 'submeat'])
->where('published', true)
->when(!is_null($submeatModel), function (Builder $q) use ($submeatModel) {
$q->whereHas('submeat', function (Builder $q) use ($submeatModel) {
$q->where('published', true)->where('id', $submeatModel->getKey());
});
})
->orderBy('slug')
->paginate($this->productModelsPerPage);
if ($request->ajax()) {
return response()->json([
'viewsPaginate' => CollectionHelper::getViewsFromPaginateModels(
$productModels,
'fo.partials.product-card',
'model'
),
'children' => $submeatModels,
]);
}
return $this->index($productModels, null, $submeatModels);
}
/**
* Get products by a selected product model,
* filtered first by product subrange/range,
* and by meats/submeats.
*
* @param \App\Models\Product $productModel
* @return \Illuminate\Support\Collection
*/
public function getProductsFiltered(Product $productModel): \Illuminate\Support\Collection
{
/** @var int $nbrMaxProducts Maximum number of products required */
$nbrMaxProducts = 9;
/** @var \Illuminate\Support\Collection<\App\Models\Product> $productModels products by same product */
$productModels = Product::query()
->whereNot('id', $productModel->getKey())
->where('published', true)
->when(!is_null($productModel->range), function (Builder $q) use ($productModel) {
$q->whereHas('range', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->range->getKey());
})->orWhereHas('subrange', function (Builder $q) use ($productModel) {
$q->whereHas('range', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->range->getKey());
});
});
})->when(!is_null($productModel->subrange), function (Builder $q) use ($productModel) {
$q->whereHas('subrange', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->subrange->getKey());
});
})
->get();
if (count($productModels) < $nbrMaxProducts) {
$test = Product::query()
->whereNot('id', $productModel->getKey())
->where('published', true)
->when(!is_null($productModel->meat), function (Builder $q) use ($productModel) {
$q->whereHas('meat', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->meat->getKey());
})->orWhereHas('submeat', function (Builder $q) use ($productModel) {
$q->whereHas('meat', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->meat->getKey());
});
});
})->when(!is_null($productModel->submeat), function (Builder $q) use ($productModel) {
$q->whereHas('submeat', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->submeat->getKey());
});
})
->get();
}
return $productModels->slice(0, $nbrMaxProducts);
}
/**
* Get recipes by a selected product model,
* filtered first by products associated,
* by meats/submeats and finally by consumption moment.
*
* @param \App\Models\Product $productModel
* @return \Illuminate\Support\Collection
*/
public function getRecipesFiltered(Product $productModel): \Illuminate\Support\Collection
{
/** @var int $nbrMaxRecipes Maximum number of recipes required */
$nbrMaxRecipes = 9;
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeModels Recipes by same product */
$recipeModels = Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->where('published', true)
->whereHas('products', function (Builder $q) use ($productModel) {
$q->where('id', $productModel->getKey());
})
->get();
if (count($recipeModels) < $nbrMaxRecipes) {
/** @var \Illuminate\Support\Collection<\App\Models\Recipe> $recipeModels Recipes by same product range */
$recipeModels = $recipeModels->merge(Recipe::query()
->with(['products', 'products.meat', 'products.submeat', 'consumptionMoment'])
->where('published', true)
->whereHas('products', function (Builder $q) use ($productModel) {
$q->when(!is_null($productModel->range), function (Builder $q) use ($productModel) {
$q->whereHas('range', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->range->getKey());
})->orWhereHas('subrange', function (Builder $q) use ($productModel) {
$q->whereHas('range', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->range->getKey());
});
});
})->when(!is_null($productModel->subrange), function (Builder $q) use ($productModel) {
$q->whereHas('subrange', function (Builder $q) use ($productModel) {
$q->where('published', true)->where('id', $productModel->subrange->getKey());
});
});
})
->get());
} //end if
return $recipeModels->slice(0, $nbrMaxRecipes);
}
/**
* Get products by a specific range into a json response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonGetProductsViewsByRange(Request $request): \Illuminate\Http\JsonResponse
{
return response()->json(Product::getProductsViewsByRange($request->LEGENDSLUG));
}
/**
* Delete the "sub" start filter to avoid "subsubrange" when user has some filters
* and hard visit a total different product filter
*
* @param string $filter
* @return mixed
*/
private function cleanSubSubFilter(string $filter): mixed
{
return str_starts_with($filter, "sub") ? preg_replace('/^sub/', '', $filter) : $filter;
}
}