— tag slugs (AND match) * category string * orientation string — landscape | portrait | square * resolution string — e.g. "1920x1080" * author_id int * sort string — created_at|downloads|likes|views (suffix :asc or :desc) */ public function search(string $q, array $filters = [], int $perPage = 24): LengthAwarePaginator { $filterParts = [self::BASE_FILTER]; $sort = []; if (! empty($filters['tags'])) { foreach ((array) $filters['tags'] as $tag) { $filterParts[] = 'tags = "' . addslashes((string) $tag) . '"'; } } if (! empty($filters['category'])) { $filterParts[] = 'category = "' . addslashes((string) $filters['category']) . '"'; } if (! empty($filters['orientation'])) { $filterParts[] = 'orientation = "' . addslashes((string) $filters['orientation']) . '"'; } if (! empty($filters['resolution'])) { $filterParts[] = 'resolution = "' . addslashes((string) $filters['resolution']) . '"'; } if (! empty($filters['author_id'])) { $filterParts[] = 'author_id = ' . (int) $filters['author_id']; } if (! empty($filters['sort'])) { [$field, $dir] = $this->parseSort((string) $filters['sort']); if ($field) { $sort[] = $field . ':' . $dir; } } $options = ['filter' => implode(' AND ', $filterParts)]; if ($sort !== []) { $options['sort'] = $sort; } return Artwork::search($q ?: '') ->options($options) ->paginate($perPage); } /** * Load artworks for a tag page, sorted by views + likes descending. */ public function byTag(string $slug, int $perPage = 24): LengthAwarePaginator { $tag = Tag::where('slug', $slug)->first(); if (! $tag) { return $this->emptyPaginator($perPage); } $cacheKey = "search.tag.{$slug}.page." . request()->get('page', 1); return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($slug, $perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER . ' AND tags = "' . addslashes($slug) . '"', 'sort' => ['views:desc', 'likes:desc'], ]) ->paginate($perPage); }); } /** * Load artworks for a category, sorted by created_at desc. */ public function byCategory(string $cat, int $perPage = 24, array $filters = []): LengthAwarePaginator { $cacheKey = "search.cat.{$cat}.page." . request()->get('page', 1); return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($cat, $perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($cat) . '"', 'sort' => ['created_at:desc'], ]) ->paginate($perPage); }); } /** * Related artworks: same tags, different artwork, ranked by views + likes. * Limit 12. */ public function related(Artwork $artwork, int $limit = 12): LengthAwarePaginator { $tags = $artwork->tags()->pluck('tags.slug')->values()->all(); if ($tags === []) { return $this->popular($limit); } $cacheKey = "search.related.{$artwork->id}"; return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($artwork, $tags, $limit) { $tagFilters = implode(' OR ', array_map( fn ($t) => 'tags = "' . addslashes($t) . '"', $tags )); return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER . ' AND id != ' . $artwork->id . ' AND (' . $tagFilters . ')', 'sort' => ['views:desc', 'likes:desc'], ]) ->paginate($limit); }); } /** * Most popular artworks by views. */ public function popular(int $perPage = 24): LengthAwarePaginator { return Cache::remember('search.popular.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['views:desc', 'likes:desc'], ]) ->paginate($perPage); }); } /** * Most recent artworks by created_at. */ public function recent(int $perPage = 24): LengthAwarePaginator { return Cache::remember('search.recent.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['created_at:desc'], ]) ->paginate($perPage); }); } // ── Discover section helpers ─────────────────────────────────────────────── /** * Trending: most viewed artworks, weighted toward recent uploads. * Uses views:desc + recency via created_at:desc as tiebreaker. */ public function discoverTrending(int $perPage = 24): LengthAwarePaginator { $page = (int) request()->get('page', 1); return Cache::remember("discover.trending.{$page}", self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['views:desc', 'created_at:desc'], ]) ->paginate($perPage); }); } /** * Fresh: newest uploads first. */ public function discoverFresh(int $perPage = 24): LengthAwarePaginator { $page = (int) request()->get('page', 1); return Cache::remember("discover.fresh.{$page}", self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['created_at:desc'], ]) ->paginate($perPage); }); } /** * Top rated: highest number of favourites/likes. */ public function discoverTopRated(int $perPage = 24): LengthAwarePaginator { $page = (int) request()->get('page', 1); return Cache::remember("discover.top-rated.{$page}", self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['likes:desc', 'views:desc'], ]) ->paginate($perPage); }); } /** * Most downloaded: highest download count. */ public function discoverMostDownloaded(int $perPage = 24): LengthAwarePaginator { $page = (int) request()->get('page', 1); return Cache::remember("discover.most-downloaded.{$page}", self::CACHE_TTL, function () use ($perPage) { return Artwork::search('') ->options([ 'filter' => self::BASE_FILTER, 'sort' => ['downloads:desc', 'views:desc'], ]) ->paginate($perPage); }); } // ------------------------------------------------------------------------- private function parseSort(string $sort): array { $allowed = ['created_at', 'downloads', 'likes', 'views']; $parts = explode(':', $sort, 2); $field = $parts[0] ?? ''; $dir = strtolower($parts[1] ?? 'desc') === 'asc' ? 'asc' : 'desc'; return in_array($field, $allowed, true) ? [$field, $dir] : [null, 'desc']; } private function emptyPaginator(int $perPage): LengthAwarePaginator { return new \Illuminate\Pagination\LengthAwarePaginator([], 0, $perPage); } }