|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace App\Support; |
| 4 | + |
| 5 | +use App\Models\Article; |
| 6 | +use DOMDocument; |
| 7 | +use DOMElement; |
| 8 | +use Illuminate\Support\Collection; |
| 9 | + |
| 10 | +class BlogFeed |
| 11 | +{ |
| 12 | + private const string ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'; |
| 13 | + |
| 14 | + private const string DUBLIN_CORE_NAMESPACE = 'http://purl.org/dc/elements/1.1/'; |
| 15 | + |
| 16 | + private const string MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/'; |
| 17 | + |
| 18 | + /** |
| 19 | + * @param Collection<int, Article> $articles |
| 20 | + */ |
| 21 | + public function toRss(Collection $articles): string |
| 22 | + { |
| 23 | + $document = new DOMDocument('1.0', 'UTF-8'); |
| 24 | + $document->formatOutput = true; |
| 25 | + |
| 26 | + $rss = $document->createElement('rss'); |
| 27 | + $rss->setAttribute('version', '2.0'); |
| 28 | + $rss->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:atom', self::ATOM_NAMESPACE); |
| 29 | + $rss->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:dc', self::DUBLIN_CORE_NAMESPACE); |
| 30 | + $rss->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MEDIA_NAMESPACE); |
| 31 | + $document->appendChild($rss); |
| 32 | + |
| 33 | + $channel = $document->createElement('channel'); |
| 34 | + $rss->appendChild($channel); |
| 35 | + |
| 36 | + $this->appendText($document, $channel, 'title', 'NativePHP Blog'); |
| 37 | + $this->appendText($document, $channel, 'link', route('blog')); |
| 38 | + |
| 39 | + $self = $document->createElementNS(self::ATOM_NAMESPACE, 'atom:link'); |
| 40 | + $self->setAttribute('href', route('blog.feed')); |
| 41 | + $self->setAttribute('rel', 'self'); |
| 42 | + $self->setAttribute('type', 'application/rss+xml'); |
| 43 | + $channel->appendChild($self); |
| 44 | + |
| 45 | + $this->appendText($document, $channel, 'description', 'Insights, updates, and stories from the NativePHP community.'); |
| 46 | + $this->appendText($document, $channel, 'language', 'en'); |
| 47 | + |
| 48 | + if ($articles->isNotEmpty()) { |
| 49 | + $this->appendText($document, $channel, 'lastBuildDate', $articles->max('updated_at')->toRssString()); |
| 50 | + } |
| 51 | + |
| 52 | + foreach ($articles as $article) { |
| 53 | + $this->appendItem($document, $channel, $article); |
| 54 | + } |
| 55 | + |
| 56 | + return (string) $document->saveXML(); |
| 57 | + } |
| 58 | + |
| 59 | + private function appendItem(DOMDocument $document, DOMElement $channel, Article $article): void |
| 60 | + { |
| 61 | + $item = $document->createElement('item'); |
| 62 | + $channel->appendChild($item); |
| 63 | + |
| 64 | + $url = route('article', $article); |
| 65 | + |
| 66 | + $this->appendText($document, $item, 'title', $article->title); |
| 67 | + $this->appendText($document, $item, 'link', $url); |
| 68 | + $this->appendText($document, $item, 'guid', $url)->setAttribute('isPermaLink', 'true'); |
| 69 | + $this->appendText($document, $item, 'pubDate', $article->published_at->toRssString()); |
| 70 | + |
| 71 | + if ($article->author?->name) { |
| 72 | + $creator = $document->createElementNS(self::DUBLIN_CORE_NAMESPACE, 'dc:creator'); |
| 73 | + $creator->appendChild($document->createTextNode($article->author->name)); |
| 74 | + $item->appendChild($creator); |
| 75 | + } |
| 76 | + |
| 77 | + $this->appendText($document, $item, 'description', $article->excerpt ?? ''); |
| 78 | + |
| 79 | + if ($article->og_image) { |
| 80 | + $media = $document->createElementNS(self::MEDIA_NAMESPACE, 'media:content'); |
| 81 | + $media->setAttribute('url', $article->og_image); |
| 82 | + $media->setAttribute('medium', 'image'); |
| 83 | + $item->appendChild($media); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + private function appendText(DOMDocument $document, DOMElement $parent, string $name, string $value): DOMElement |
| 88 | + { |
| 89 | + $element = $document->createElement($name); |
| 90 | + $element->appendChild($document->createTextNode($value)); |
| 91 | + $parent->appendChild($element); |
| 92 | + |
| 93 | + return $element; |
| 94 | + } |
| 95 | +} |
0 commit comments