1 * - Real result count already meets the minimum */ public function fill( LengthAwarePaginator $results, int $minimum = 0, int $page = 1, ): LengthAwarePaginator { if (! EarlyGrowth::gridFillerEnabled() || $page > 1) { return $results; } $minimum = $minimum > 0 ? $minimum : (int) config('early_growth.grid_min_results', 12); $items = $results->getCollection(); $count = $items->count(); if ($count >= $minimum) { return $results; } $needed = $minimum - $count; $exclude = $items->pluck('id')->all(); $filler = $this->fetchTrendingFiller($needed + 6, $exclude)->take($needed); $merged = $items ->concat($filler) ->unique('id') ->values(); return new LengthAwarePaginator( $merged->all(), max((int) $results->total(), $merged->count()), // never shrink reported total $results->perPage(), $page, [ 'path' => $results->path(), 'pageName' => $results->getPageName(), ] ); } /** * Fill a plain Collection (for non-paginated grids like homepage sections). */ public function fillCollection(Collection $items, int $minimum = 0): Collection { if (! EarlyGrowth::gridFillerEnabled()) { return $items; } $minimum = $minimum > 0 ? $minimum : (int) config('early_growth.grid_min_results', 12); if ($items->count() >= $minimum) { return $items; } $needed = $minimum - $items->count(); $exclude = $items->pluck('id')->all(); $filler = $this->fetchTrendingFiller($needed + 6, $exclude)->take($needed); return $items->concat($filler)->unique('id')->values(); } // ─── Private ───────────────────────────────────────────────────────────── /** * Pull high-ranking artworks as grid filler. * Cache key includes an exclude-hash so different grids get distinct content. */ private function fetchTrendingFiller(int $limit, array $excludeIds): Collection { $ttl = (int) config('early_growth.cache_ttl.feed_blend', 300); $excludeHash = md5(implode(',', array_slice(array_unique($excludeIds), 0, 50))); $cacheKey = "egs.grid_filler.{$excludeHash}.{$limit}"; return Cache::remember($cacheKey, $ttl, function () use ($limit, $excludeIds): Collection { return Artwork::query() ->public() ->published() ->with([ 'user:id,name,username', 'user.profile:user_id,avatar_hash', 'categories:id,name,slug,content_type_id,parent_id,sort_order', ]) ->leftJoin('artwork_stats as _gf_stats', '_gf_stats.artwork_id', '=', 'artworks.id') ->select('artworks.*') ->when(! empty($excludeIds), fn ($q) => $q->whereNotIn('artworks.id', $excludeIds)) ->orderByDesc('_gf_stats.ranking_score') ->limit($limit) ->get() ->values(); }); } }