FeedComponent   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 29
c 6
b 0
f 0
lcom 1
cbo 4
dl 0
loc 330
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A componentDetails() 0 7 1
B defineProperties() 0 43 1
A returnPagesList() 0 4 1
A getShowPageOptions() 0 4 1
A getEpisodePageOptions() 0 4 1
A onRun() 0 21 2
B setState() 0 28 1
A generateXML() 0 12 1
A disableConflictingPlugins() 0 9 2
B addChannelToFeed() 0 34 6
A setItunesCategories() 0 16 4
B addEpisodesToChannel() 0 27 4
A addFields() 0 10 4
1
<?php
2
3
namespace CosmicRadioTV\Podcast\components;
4
5
6
use Cms\Classes\Page;
7
use CosmicRadioTV\Podcast\Classes\ComponentBase;
8
use CosmicRadioTV\Podcast\Models\Episode;
9
use CosmicRadioTV\Podcast\Models\Release;
10
use CosmicRadioTV\Podcast\Models\ReleaseType;
11
use CosmicRadioTV\Podcast\Models\Show;
12
use Illuminate\Database\Eloquent\Builder;
13
use Illuminate\Database\Eloquent\Collection;
14
use Illuminate\Database\Eloquent\ModelNotFoundException;
15
use Illuminate\Database\Eloquent\Relations\Relation;
16
use Illuminate\Http\Response;
17
use October\Rain\Database\Model;
18
use SimpleXMLElement;
19
use System\Classes\PluginManager;
20
21
class FeedComponent extends ComponentBase
22
{
23
24
    /**
25
     * @var Collection|Episode[] All of the episodes
26
     */
27
    public $episodes;
28
29
    /**
30
     * @var ReleaseType Release type that matches the feed
31
     */
32
    public $releaseType;
33
34
    /**
35
     * @var Show The show being displayed
36
     */
37
    public $show;
38
39
    /**
40
     * @var array Fields to map to RSS feed in channel
41
     */
42
    protected $feed_channel_fields = [
43
        'description'    => ['description', 'itunes:summary'],
44
        'feed_language'  => 'language',
45
        'feed_copyright' => 'copyright',
46
        'feed_author'    => 'itunes:author'
47
    ];
48
49
    /**
50
     * @var array Fields to map to RSS feed in items
51
     */
52
    protected $feed_episode_fields = [
53
        'title'   => 'title',
54
        'summary' => ['description', 'itunes:summary'],
55
        'length'  => 'itunes:duration',
56
    ];
57
58
    /**
59
     * Returns information about this component, including name and description.
60
     *
61
     * @return array
62
     */
63
    public function componentDetails()
64
    {
65
        return [
66
            'name'        => 'cosmicradiotv.podcast::components.feed.name',
67
            'description' => 'cosmicradiotv.podcast::components.feed.description',
68
        ];
69
    }
70
71
    /**
72
     * User editable properties
73
     *
74
     * @return array
75
     */
76
    public function defineProperties()
77
    {
78
        return [
79
            'showSlug'        => [
80
                'title'       => 'cosmicradiotv.podcast::components.common.properties.show_slug.title',
81
                'description' => 'cosmicradiotv.podcast::components.common.properties.show_slug.description',
82
                'default'     => '{{ :show_slug }}',
83
                'type'        => 'string',
84
            ],
85
            'releaseTypeSlug' => [
86
                'title'       => 'cosmicradiotv.podcast::components.feed.properties.release_type_slug.title',
87
                'description' => 'cosmicradiotv.podcast::components.feed.properties.release_type_slug.description',
88
                'default'     => '{{ :release_type_slug }}',
89
                'type'        => 'string',
90
            ],
91
            'itemLimit'       => [
92
                'title'             => 'cosmicradiotv.podcast::components.feed.properties.item_limit.title',
93
                'description'       => 'cosmicradiotv.podcast::components.feed.properties.item_limit.description',
94
                'default'           => 100,
95
                'type'              => 'string',
96
                'validationPattern' => '^[0-9]+$',
97
                'validationMessage' => trans('cosmicradiotv.podcast::components.feed.properties.item_limit.validationMessage'),
98
                'required'          => true,
99
            ],
100
            'showPage'        => [
101
                'title'       => 'cosmicradiotv.podcast::components.feed.properties.show_page.title',
102
                'description' => 'cosmicradiotv.podcast::components.feed.properties.show_page.description',
103
                'type'        => 'dropdown',
104
                'default'     => 'podcast/show',
105
                'required'    => true,
106
                'group'       => trans('cosmicradiotv.podcast::components.feed.groups.links'),
107
            ],
108
            'episodePage'     => [
109
                'title'       => 'cosmicradiotv.podcast::components.feed.properties.episode_page.title',
110
                'description' => 'cosmicradiotv.podcast::components.feed.properties.episode_page.description',
111
                'type'        => 'dropdown',
112
                'default'     => 'podcast/episode',
113
                'required'    => true,
114
                'group'       => trans('cosmicradiotv.podcast::components.feed.groups.links'),
115
            ],
116
117
        ];
118
    }
119
120
    /**
121
     * Return pages list for dropdowns
122
     *
123
     * @return array
124
     */
125
    protected function returnPagesList()
126
    {
127
        return Page::getNameList();
128
    }
129
130
    public function getShowPageOptions()
131
    {
132
        return $this->returnPagesList();
133
    }
134
135
    public function getEpisodePageOptions()
136
    {
137
        return $this->returnPagesList();
138
    }
139
140
141
    /**
142
     * Generates RSS feed and overwrites page with it
143
     *
144
     * @return \Symfony\Component\HttpFoundation\Response
145
     */
146
    public function onRun()
147
    {
148
        try {
149
            $this->setState();
150
        } catch (ModelNotFoundException $e) {
151
            // Show not found, return 404
152
            $this->controller->setStatusCode(404);
153
154
            return $this->controller->run('404');
155
        }
156
157
        $feed = $this->generateXML();
158
159
        $this->disableConflictingPlugins();
160
161
        $response = Response::create($feed->asXML(), 200, [
162
            'Content-Type' => 'application/rss+xml',
163
        ]);
164
165
        return $response;
166
    }
167
168
169
    /**
170
     * Set components state based on parameters
171
     *
172
     * @throws ModelNotFoundException
173
     */
174
    public function setState()
175
    {
176
        $this->show = $show = Show::query()
177
                                  ->where('slug', $this->property('showSlug'))
178
                                  ->firstOrFail();
179
        $this->releaseType = $releaseType = ReleaseType::query()
180
                                                       ->where('slug', $this->property('releaseTypeSlug'))
181
                                                       ->firstOrFail();
182
        $this->episodes = $this->show->episodes()
183
                                     ->with([
184
                                         'releases' => function (Relation $query) use ($releaseType) {
185
                                             /** @var Relation|Builder $query */
186
                                             $query->where('release_type_id', $releaseType->id);
187
                                         }
188
                                     ])
189
                                     ->where('published', true)
190
                                     ->whereHas('releases', function (Builder $query) use ($releaseType) {
191
                                         $query->where('release_type_id', $releaseType->id);
192
                                     })
193
                                     ->orderBy('release', 'desc')
194
                                     ->take(intval($this->property('itemLimit')))
195
                                     ->get();
196
197
        $this->episodes->map(function (Episode $episode) use ($show) {
198
            $episode->setRelation('show', $show);
199
        });
200
201
    }
202
203
204
    /**
205
     * Generates an xml feed based on show
206
     *
207
     * @return SimpleXMLElement
208
     */
209
    public function generateXML()
210
    {
211
        $root = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><rss></rss>');
212
        $root['version'] = '2.0';
213
        $root['xmlns:atom'] = 'http://www.w3.org/2005/Atom';
214
        $root['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
215
216
        $channel = $this->addChannelToFeed($root);
217
        $this->addEpisodesToChannel($channel);
218
219
        return $root;
220
    }
221
222
    /**
223
     * Disables any other plugins that are known to butt in
224
     */
225
    protected function disableConflictingPlugins()
226
    {
227
        $pluginManager = PluginManager::instance();
228
229
        // Debugbar - Injects extra crap into xml
230
        if ($pluginManager->exists('bedard.debugbar')) {
231
            app('config')->set('debugbar.enabled', false);
232
        }
233
    }
234
235
    /**
236
     * Adds channel to the feed
237
     *
238
     * @param SimpleXMLElement $root
239
     *
240
     * @return SimpleXMLElement
241
     */
242
    protected function addChannelToFeed(SimpleXMLElement $root)
243
    {
244
        $channel = $root->addChild('channel');
245
        $channel->title = "{$this->show->name} ({$this->releaseType->name})";
246
        $this->addFields($channel, $this->feed_channel_fields, $this->show);
247
248
        $channel->link = $this->controller->pageUrl($this->property('showPage'), ['show_slug' => $this->show->slug]);
249
        $channel->generator = 'CosmicRadioTV/podcast-plugin';
250
        $channel->docs = 'http://blogs.law.harvard.edu/tech/rss';
251
        $channel->{'atom:link'}['rel'] = 'self';
252
        $channel->{'atom:link'}['href'] = $this->controller->currentPageUrl();
253
254
        if ($this->show->image) {
255
            $channel->image->url = asset($this->show->image->getPath());
256
            $channel->image->title = $channel->title;
257
            $channel->image->link = $channel->link;
258
            $channel->{'itunes:image'}['href'] = asset($this->show->image->getPath());
259
        }
260
        // iTunes categories
261
        if ($this->show->itunes_category) {
262
            $this->setItunesCategories($channel);
263
        }
264
        if ($this->show->itunes_explicit) {
265
            $channel->{'itunes:explicit'} = 'yes';
266
        }
267
        if ($this->show->itunes_owner_name) {
268
            $channel->{'itunes:owner'}->{'itunes:name'} = $this->show->itunes_owner_name;
269
        }
270
        if ($this->show->itunes_owner_email) {
271
            $channel->{'itunes:owner'}->{'itunes:email'} = $this->show->itunes_owner_email;
272
        }
273
274
        return $channel;
275
    }
276
277
    /**
278
     * Set iTunes categories on the feed
279
     *
280
     * @param SimpleXMLElement $channel
281
     */
282
    protected function setItunesCategories(SimpleXMLElement $channel)
283
    {
284
        $categories = explode("\r\n", $this->show->itunes_category);
285
        /** @var SimpleXMLElement $previous */
286
        $previous = null;
287
        foreach ($categories as $category) {
288
            if (substr($category, 0, 1) == ' ' && isset($previous)) {
289
                $element = $previous->addChild('xmlns:itunes:category');
290
                $text = substr($category, 1);
291
            } else {
292
                $previous = $element = $channel->addChild('xmlns:itunes:category');
293
                $text = $category;
294
            }
295
            $element['text'] = $text;
296
        }
297
    }
298
299
    /**
300
     * Add episodes to the channel
301
     *
302
     * @param SimpleXMLElement $channel
303
     */
304
    protected function addEpisodesToChannel(SimpleXMLElement $channel)
305
    {
306
        foreach ($this->episodes as $episode) {
307
            $episodeNode = $channel->addChild('item');
308
309
            $this->addFields($episodeNode, $this->feed_episode_fields, $episode);
310
311
            $episodeNode->link = $this->controller->pageUrl($this->property('episodePage'),
312
                ['show_slug' => $this->show->slug, 'episode_slug' => $episode->slug]);
313
            $episodeNode->guid = $episodeNode->link;
314
            $episodeNode->pubDate = $episode->release->toRfc2822String();
315
            if ($episode->itunes_explicit) {
316
                $episodeNode->{'itunes:explicit'} = 'yes';
317
            }
318
319
            /** @var Release $release */
320
            $release = $episode->releases->first(); // Filtered to be the one in eager loader
321
            if (in_array($this->releaseType->type, ['audio', 'video'])) {
322
                $episodeNode->enclosure['url'] = $release->url;
323
                $episodeNode->enclosure['length'] = $release->size;
324
                $episodeNode->enclosure['type'] = $this->releaseType->filetype;
325
            } else {
326
                $episodeNode->comments = $episodeNode->link;
327
                $episodeNode->link = $release->url;
328
            }
329
        }
330
    }
331
332
    /**
333
     * Attaches fields from model to XML element
334
     *
335
     * @param SimpleXMLElement $channel
336
     * @param array            $fields
337
     * @param Model            $souceModel
338
     */
339
    protected function addFields(SimpleXMLElement $channel, $fields, Model $souceModel)
340
    {
341
        foreach ($fields as $source => $target) {
342
            if ($souceModel->{$source}) {
343
                foreach ((array) $target as $field) {
344
                    $channel->{$field} = $souceModel->{$source};
345
                }
346
            }
347
        }
348
    }
349
350
}