Passed
Push — master ( 32dd3a...b6a4c4 )
by Caen
03:02 queued 12s
created

RssFeedService   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Importance

Changes 19
Bugs 1 Features 1
Metric Value
wmc 17
eloc 45
c 19
b 1
f 1
dl 0
loc 110
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getXML() 0 3 1
A addDynamicItemData() 0 24 6
A addItem() 0 7 1
A getImageLength() 0 4 1
A generate() 0 6 1
A generateFeed() 0 3 1
A getImageType() 0 4 2
A getDescription() 0 5 1
A addBaseChannelItems() 0 14 1
A outputFilename() 0 3 1
1
<?php
2
3
/** @noinspection PhpComposerExtensionStubsInspection */
4
/** @noinspection XmlUnusedNamespaceDeclaration */
5
6
declare(strict_types=1);
7
8
namespace Hyde\Framework\Services;
9
10
use function config;
11
use function date;
12
use Exception;
13
use function extension_loaded;
14
use Hyde\Facades\Site;
15
use Hyde\Hyde;
16
use Hyde\Pages\MarkdownPost;
17
use Hyde\Support\Helpers\XML;
18
use SimpleXMLElement;
19
use function throw_unless;
20
21
/**
22
 * @see \Hyde\Framework\Testing\Feature\Services\RssFeedServiceTest
23
 * @see https://validator.w3.org/feed/docs/rss2.html
24
 * @phpstan-consistent-constructor
25
 */
26
class RssFeedService
27
{
28
    public SimpleXMLElement $feed;
29
30
    public static function generateFeed(): string
31
    {
32
        return (new static)->generate()->getXML();
33
    }
34
35
    public static function outputFilename(): string
36
    {
37
        return config('hyde.rss_filename', 'feed.xml');
38
    }
39
40
    public static function getDescription(): string
41
    {
42
        return XML::escape(config(
43
            'hyde.rss_description',
44
            XML::escape(Site::name()).' RSS Feed'
0 ignored issues
show
Bug introduced by
It seems like Hyde\Facades\Site::name() can also be of type null; however, parameter $string of Hyde\Support\Helpers\XML::escape() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

44
            XML::escape(/** @scrutinizer ignore-type */ Site::name()).' RSS Feed'
Loading history...
45
        ));
46
    }
47
48
    public function __construct()
49
    {
50
        throw_unless(extension_loaded('simplexml'),
51
            new Exception('The ext-simplexml extension is not installed, but is required to generate RSS feeds.')
52
        );
53
54
        $this->feed = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>
55
            <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" />');
56
        $this->feed->addChild('channel');
57
58
        $this->addBaseChannelItems();
59
    }
60
61
    public function generate(): static
62
    {
63
        MarkdownPost::getLatestPosts()
64
            ->each(fn (MarkdownPost $post) => $this->addItem($post));
65
66
        return $this;
67
    }
68
69
    public function getXML(): string
70
    {
71
        return (string) $this->feed->asXML();
72
    }
73
74
    protected function addItem(MarkdownPost $post): void
75
    {
76
        $item = $this->feed->channel->addChild('item');
77
        $item->addChild('title', $post->title);
78
        $item->addChild('description', $post->description);
79
80
        $this->addDynamicItemData($item, $post);
81
    }
82
83
    protected function addDynamicItemData(SimpleXMLElement $item, MarkdownPost $post): void
84
    {
85
        if (isset($post->canonicalUrl)) {
86
            $item->addChild('link', $post->canonicalUrl);
87
            $item->addChild('guid', $post->canonicalUrl);
88
        }
89
90
        if (isset($post->date)) {
91
            $item->addChild('pubDate', $post->date->dateTimeObject->format(DATE_RSS));
92
        }
93
94
        if (isset($post->author)) {
95
            $item->addChild('dc:creator', $post->author->getName(), 'http://purl.org/dc/elements/1.1/');
0 ignored issues
show
Bug introduced by
The method getName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
            $item->addChild('dc:creator', $post->author->/** @scrutinizer ignore-call */ getName(), 'http://purl.org/dc/elements/1.1/');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
96
        }
97
98
        if (isset($post->category)) {
99
            $item->addChild('category', $post->category);
100
        }
101
102
        if (isset($post->image)) {
103
            $image = $item->addChild('enclosure');
104
            $image->addAttribute('url', Hyde::image($post->image->getSource(), true));
0 ignored issues
show
Bug introduced by
The method getSource() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
            $image->addAttribute('url', Hyde::image($post->image->/** @scrutinizer ignore-call */ getSource(), true));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
105
            $image->addAttribute('type', $this->getImageType($post));
106
            $image->addAttribute('length', $this->getImageLength($post));
107
        }
108
    }
109
110
    protected function addBaseChannelItems(): void
111
    {
112
        $this->feed->channel->addChild('title', XML::escape(Site::name()));
0 ignored issues
show
Bug introduced by
It seems like Hyde\Facades\Site::name() can also be of type null; however, parameter $string of Hyde\Support\Helpers\XML::escape() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

112
        $this->feed->channel->addChild('title', XML::escape(/** @scrutinizer ignore-type */ Site::name()));
Loading history...
113
        $this->feed->channel->addChild('link', XML::escape(Site::url()));
114
        $this->feed->channel->addChild('description', static::getDescription());
115
116
        $this->feed->channel->addChild('language', config('site.language', 'en'));
117
        $this->feed->channel->addChild('generator', 'HydePHP '.Hyde::version());
118
        $this->feed->channel->addChild('lastBuildDate', date(DATE_RSS));
119
120
        $atomLink = $this->feed->channel->addChild('atom:link', namespace: 'http://www.w3.org/2005/Atom');
121
        $atomLink->addAttribute('href', XML::escape(Hyde::url(static::outputFilename())));
122
        $atomLink->addAttribute('rel', 'self');
123
        $atomLink->addAttribute('type', 'application/rss+xml');
124
    }
125
126
    protected function getImageType(MarkdownPost $post): string
127
    {
128
        /** @todo Add support for more types */
129
        return str_ends_with($post->image->getSource(), '.png') ? 'image/png' : 'image/jpeg';
130
    }
131
132
    protected function getImageLength(MarkdownPost $post): string
133
    {
134
        /** @todo We might want to add a build warning if the length is zero */
135
        return (string) $post->image->getContentLength();
136
    }
137
}
138