take(self::FEED_LIMIT)->map(fn ($a) => $this->artworkToItem($a)); return $this->buildResponse($channelTitle, $channelDescription, url('/'), $feedUrl, $items); } /** * Build an RSS 2.0 Response from a BlogPost Eloquent collection. * Posts must have 'author' relation preloaded. */ public function buildFromBlogPosts( string $channelTitle, string $channelDescription, string $feedUrl, Collection $posts, ): Response { $items = $posts->take(self::FEED_LIMIT)->map(fn ($p) => $this->blogPostToItem($p)); return $this->buildResponse($channelTitle, $channelDescription, url('/blog'), $feedUrl, $items); } // ── Private helpers ─────────────────────────────────────────────────────── private function buildResponse( string $channelTitle, string $channelDescription, string $channelLink, string $feedUrl, Collection $items, ): Response { $xml = view('rss.channel', [ 'channelTitle' => trim($channelTitle) . ' — Skinbase', 'channelDescription' => $channelDescription, 'channelLink' => $channelLink, 'feedUrl' => $feedUrl, 'items' => $items, 'buildDate' => now()->toRfc2822String(), ])->render(); return response($xml, 200, [ 'Content-Type' => 'application/rss+xml; charset=utf-8', 'Cache-Control' => 'public, max-age=300', ]); } /** Convert an Artwork model to an RSS item array. */ private function artworkToItem(object $artwork): array { $link = url('/art/' . $artwork->id . '/' . ($artwork->slug ?? '')); $thumb = method_exists($artwork, 'thumbUrl') ? $artwork->thumbUrl('sm') : null; // Primary category from eagerly loaded relation (avoid N+1) $primaryCategory = ($artwork->relationLoaded('categories')) ? $artwork->categories->first() : null; // Build HTML description embedded in CDATA $descParts = []; if ($thumb) { $descParts[] = ''; } if (!empty($artwork->description)) { $descParts[] = '

' . htmlspecialchars(strip_tags((string) $artwork->description), ENT_XML1) . '

'; } return [ 'title' => (string) $artwork->title, 'link' => $link, 'guid' => $link, 'description' => implode('', $descParts), 'pubDate' => $artwork->published_at?->toRfc2822String(), 'author' => $artwork->user?->username ?? 'Unknown', 'category' => $primaryCategory?->name, 'enclosure' => $thumb ? [ 'url' => $thumb, 'length' => 0, 'type' => 'image/jpeg', ] : null, ]; } /** Convert a BlogPost model to an RSS item array. */ private function blogPostToItem(object $post): array { $link = url('/blog/' . $post->slug); $excerpt = $post->excerpt ?? strip_tags((string) ($post->body ?? '')); return [ 'title' => (string) $post->title, 'link' => $link, 'guid' => $link, 'description' => $excerpt, 'pubDate' => $post->published_at?->toRfc2822String(), 'author' => $post->author?->username ?? 'Skinbase', 'category' => null, 'enclosure' => !empty($post->featured_image) ? [ 'url' => $post->featured_image, 'length' => 0, 'type' => 'image/jpeg', ] : null, ]; } }