1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** @noinspection PhpComposerExtensionStubsInspection */ |
4
|
|
|
/** @noinspection XmlUnusedNamespaceDeclaration */ |
5
|
|
|
|
6
|
|
|
namespace Hyde\Framework\Services; |
7
|
|
|
|
8
|
|
|
use Hyde\Framework\Hyde; |
9
|
|
|
use Hyde\Framework\Models\Pages\MarkdownPost; |
10
|
|
|
use SimpleXMLElement; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* @see \Hyde\Framework\Testing\Feature\Services\RssFeedServiceTest |
14
|
|
|
* @see https://validator.w3.org/feed/docs/rss2.html |
15
|
|
|
* @phpstan-consistent-constructor |
16
|
|
|
*/ |
17
|
|
|
class RssFeedService |
18
|
|
|
{ |
19
|
|
|
public SimpleXMLElement $feed; |
20
|
|
|
|
21
|
|
|
public function __construct() |
22
|
|
|
{ |
23
|
|
|
if (! extension_loaded('simplexml') || config('testing.mock_disabled_extensions', false) === true) { |
24
|
|
|
throw new \Exception('The ext-simplexml extension is not installed, but is required to generate RSS feeds.'); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
$this->feed = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?> |
28
|
|
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" />'); |
29
|
|
|
$this->feed->addChild('channel'); |
30
|
|
|
|
31
|
|
|
$this->addInitialChannelItems(); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @throws \Exception |
36
|
|
|
*/ |
37
|
|
|
public function generate(): static |
38
|
|
|
{ |
39
|
|
|
/** @var MarkdownPost $post */ |
40
|
|
|
foreach (MarkdownPost::getLatestPosts() as $post) { |
41
|
|
|
$this->addItem($post); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
return $this; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
public function getXML(): string|bool |
48
|
|
|
{ |
49
|
|
|
return $this->feed->asXML(); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
protected function addItem(MarkdownPost $post): void |
53
|
|
|
{ |
54
|
|
|
$item = $this->feed->channel->addChild('item'); |
55
|
|
|
$item->addChild('title', $post->title); |
56
|
|
|
$item->addChild('link', $post->getCanonicalLink()); |
|
|
|
|
57
|
|
|
$item->addChild('guid', $post->getCanonicalLink()); |
|
|
|
|
58
|
|
|
$item->addChild('description', $post->getPostDescription()); |
|
|
|
|
59
|
|
|
|
60
|
|
|
$this->addAdditionalItemData($item, $post); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
protected function addAdditionalItemData(SimpleXMLElement $item, MarkdownPost $post): void |
64
|
|
|
{ |
65
|
|
|
if (isset($post->date)) { |
66
|
|
|
$item->addChild('pubDate', $post->date->dateTimeObject->format(DATE_RSS)); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
if (isset($post->author)) { |
70
|
|
|
$item->addChild('dc:creator', $post->author->getName(), 'http://purl.org/dc/elements/1.1/'); |
|
|
|
|
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
if (isset($post->category)) { |
74
|
|
|
$item->addChild('category', $post->category); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
if (isset($post->image)) { |
78
|
|
|
$image = $item->addChild('enclosure'); |
79
|
|
|
$image->addAttribute('url', Hyde::image($post->image, true)); |
80
|
|
|
$image->addAttribute('type', str_ends_with($post->image->getSource(), '.png') ? 'image/png' : 'image/jpeg'); |
|
|
|
|
81
|
|
|
$image->addAttribute('length', $post->image->getContentLength()); |
82
|
|
|
} |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
protected function addInitialChannelItems(): void |
86
|
|
|
{ |
87
|
|
|
$this->feed->channel->addChild('title', static::getTitle()); |
88
|
|
|
$this->feed->channel->addChild('link', static::getLink()); |
89
|
|
|
$this->feed->channel->addChild('description', static::getDescription()); |
90
|
|
|
|
91
|
|
|
$atomLink = $this->feed->channel->addChild('atom:link', namespace: 'http://www.w3.org/2005/Atom'); |
92
|
|
|
$atomLink->addAttribute('href', static::getLink().'/'.static::getDefaultOutputFilename()); |
93
|
|
|
$atomLink->addAttribute('rel', 'self'); |
94
|
|
|
$atomLink->addAttribute('type', 'application/rss+xml'); |
95
|
|
|
|
96
|
|
|
$this->addAdditionalChannelData(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
protected function addAdditionalChannelData(): void |
100
|
|
|
{ |
101
|
|
|
$this->feed->channel->addChild('language', config('site.language', 'en')); |
102
|
|
|
$this->feed->channel->addChild('generator', 'HydePHP '.Hyde::version()); |
103
|
|
|
$this->feed->channel->addChild('lastBuildDate', date(DATE_RSS)); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
protected static function xmlEscape(string $string): string |
107
|
|
|
{ |
108
|
|
|
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
public static function getDescription(): string |
112
|
|
|
{ |
113
|
|
|
return static::xmlEscape( |
114
|
|
|
config( |
115
|
|
|
'hyde.rss_description', |
116
|
|
|
static::getTitle().' RSS Feed' |
117
|
|
|
) |
118
|
|
|
); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
public static function getTitle(): string |
122
|
|
|
{ |
123
|
|
|
return static::xmlEscape( |
124
|
|
|
config('site.name', 'HydePHP') |
125
|
|
|
); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
public static function getLink(): string |
129
|
|
|
{ |
130
|
|
|
return static::xmlEscape( |
131
|
|
|
rtrim( |
132
|
|
|
config('site.url') ?? 'http://localhost', |
133
|
|
|
'/' |
134
|
|
|
) |
135
|
|
|
); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
public static function getDefaultOutputFilename(): string |
139
|
|
|
{ |
140
|
|
|
return config('hyde.rss_filename', 'feed.xml'); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
public static function generateFeed(): string |
144
|
|
|
{ |
145
|
|
|
return (new static)->generate()->getXML(); |
|
|
|
|
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.