canonicalQueryParameters($request); $canonicalUrl = $this->canonicalSearchUrl($request, $canonicalQuery); if ($request->fullUrl() !== $canonicalUrl) { return redirect()->to($canonicalUrl, 301); } $q = (string) ($canonicalQuery['q'] ?? ''); $sort = (string) ($canonicalQuery['sort'] ?? 'latest'); $hasQuery = $q !== ''; $sortMap = [ 'popular' => 'views:desc', 'likes' => 'likes:desc', 'latest' => 'created_at:desc', 'downloads' => 'downloads:desc', ]; $artworks = $hasQuery ? $this->search->search($q, [ 'sort' => ($sortMap[$sort] ?? 'created_at:desc'), ]) : $this->search->popular(24); $groups = $hasQuery ? $this->groups->searchCards($q, $request->user(), 6) : $this->groups->surfaceCards($request->user(), 'featured', 4); $news = $hasQuery ? NewsArticle::query() ->with(['author:id,username,name', 'category:id,name,slug']) ->published() ->where(function ($builder) use ($q): void { $builder->where('title', 'like', '%' . $q . '%') ->orWhere('excerpt', 'like', '%' . $q . '%') ->orWhere('content', 'like', '%' . $q . '%') ->orWhere('meta_title', 'like', '%' . $q . '%'); }) ->editorialOrder() ->limit(4) ->get() : collect(); $groupResults = collect($groups ?? []); $newsResults = collect($news ?? []); $resultCount = method_exists($artworks, 'total') ? (int) $artworks->total() : 0; $groupResultCount = $groupResults->count(); $newsResultCount = $newsResults->count(); $hasAnyResults = $resultCount > 0 || $groupResultCount > 0 || $newsResultCount > 0; $galleryItems = method_exists($artworks, 'getCollection') ? $artworks->getCollection() : new EloquentCollection(collect($artworks)->all()); $galleryItems->loadMissing(['user.profile', 'group', 'categories.contentType']); $galleryArtworks = $galleryItems ->map(fn ($artwork) => (new ArtworkListResource($artwork))->resolve($request)) ->values() ->all(); $galleryNextPageUrl = method_exists($artworks, 'nextPageUrl') ? $artworks->nextPageUrl() : null; return view('search.index', [ 'q' => $q, 'hasQuery' => $hasQuery, 'sort' => $sort, 'groups' => $groups, 'groupResults' => $groupResults, 'groupResultCount' => $groupResultCount, 'artworks' => $artworks, 'resultCount' => $resultCount, 'news' => $news, 'newsResults' => $newsResults, 'newsResultCount' => $newsResultCount, 'hasAnyResults' => $hasAnyResults, 'galleryArtworks' => $galleryArtworks, 'galleryNextPageUrl' => $galleryNextPageUrl, 'page_title' => $hasQuery ? 'Search: ' . $q . ' — Skinbase' : 'Search — Skinbase', 'page_meta_description' => 'Search Skinbase for artworks, creators, groups, photography, wallpapers and skins.', 'page_robots' => 'noindex,follow', ]); } /** * @return array */ private function canonicalQueryParameters(Request $request): array { $q = $this->normalizeSearchQuery($request->query('q', '')); if ($q === '') { return []; } $params = ['q' => $q]; $sort = $this->normalizeSort($request->query('sort', 'latest')); $page = $this->normalizePage($request->query('page', 1)); if ($sort !== 'latest') { $params['sort'] = $sort; } if ($page > 1) { $params['page'] = $page; } return $params; } /** * @param array $params */ private function canonicalSearchUrl(Request $request, array $params): string { $query = Arr::query($params); return $query === '' ? $request->url() : $request->url() . '?' . $query; } private function normalizeSearchQuery(mixed $value): string { $query = html_entity_decode($this->firstScalarValue($value), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $query = preg_replace('/(?:\?|&)(?:amp;)?(?:page|sort|filter|group|id|txtfilter|q)=.*$/i', '', $query) ?? $query; $query = preg_replace('/\s+/u', ' ', $query) ?? $query; return trim($query, " \t\n\r\0\x0B?&"); } private function normalizeSort(mixed $value): string { $sort = strtolower($this->firstScalarValue($value)); $sort = preg_replace('/(?:\?|&).*/', '', $sort) ?? $sort; return in_array($sort, self::ALLOWED_SORTS, true) ? $sort : 'latest'; } private function normalizePage(mixed $value): int { $page = $this->firstScalarValue($value); if (preg_match('/\d+/', $page, $matches) !== 1) { return 1; } return max(1, (int) $matches[0]); } private function firstScalarValue(mixed $value): string { if (is_array($value)) { $value = reset($value); } if (! is_scalar($value) && $value !== null) { return ''; } return trim((string) $value); } }