|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; } }