Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RssController
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 3
30
0.00% covered (danger)
0.00%
0 / 1
 beforeFilter
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 viewClasses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 index
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2declare(strict_types=1);
3
4namespace App\Controller;
5
6use App\Utility\SettingsManager;
7use Cake\Cache\Cache;
8use Cake\Event\EventInterface;
9use Cake\Routing\Router;
10use Cake\View\XmlView;
11
12/**
13 * RssController handles the generation of RSS feeds for the application.
14 */
15class RssController extends AppController
16{
17    /**
18     * Before filter method to allow unauthenticated access to the index action.
19     *
20     * @param \Cake\Event\EventInterface $event The event object.
21     * @return void
22     */
23    public function beforeFilter(EventInterface $event): void
24    {
25        parent::beforeFilter($event);
26        $this->Authentication->allowUnauthenticated(['index']);
27    }
28
29    /**
30     * Specifies the view classes to be used for rendering the RSS feed.
31     *
32     * @return array An array of view class names.
33     */
34    public function viewClasses(): array
35    {
36        return [XmlView::class];
37    }
38
39    /**
40     * Generates the RSS feed for the latest articles.
41     *
42     * This method fetches published articles, constructs the RSS feed metadata,
43     * and sets the necessary view options and response headers.
44     *
45     * @return void
46     */
47    public function index(): void
48    {
49        $currentLang = $this->request->getParam('lang', 'en');
50        $siteName = SettingsManager::read('SEO.siteName');
51
52        $articlesTable = $this->fetchTable('Articles');
53
54        // Get published articles
55        $cacheKey = $this->cacheKey;
56        $articles = Cache::read($cacheKey, 'content');
57        if (!$articles) {
58            $articles = $articlesTable->find('all')
59                ->select(['id', 'title', 'slug', 'summary', 'created'])
60                ->where([
61                    'kind' => 'article',
62                    'is_published' => true,
63                ])
64                ->orderByDesc('created')
65                ->all();
66            Cache::write($cacheKey, $articles, 'content');
67        }
68
69        $siteUrl = Router::url(['_name' => 'home', 'lang' => $currentLang], true);
70
71        // Build channel metadata
72        $channelData = [
73            'title' => __('Latest Articles from {0}', $siteName),
74            'link' => $siteUrl,
75            'description' => __('Latest articles and updates from our website'),
76            'language' => $currentLang,
77            'copyright' => 'Copyright ' . date('Y') . ' ' . $siteName,
78            'generator' => $siteName,
79            'docs' => 'https://www.sitemaps.org/protocol.html',
80        ];
81
82        // Add image data
83        $channelData['image'] = [
84            'url' => Router::url('/img/logo.png', true),
85            'title' => __('Latest Articles from {0}', $siteName),
86            'link' => $siteUrl,
87        ];
88
89        // Add items
90        foreach ($articles as $article) {
91            $articleUrl = Router::url([
92                '_name' => 'article-by-slug',
93                'slug' => $article->slug,
94                'lang' => $currentLang,
95            ], true);
96
97            $channelData['item'][] = [
98                'title' => $article->title,
99                'link' => $articleUrl,
100                'description' => strip_tags($article->summary),
101                'pubDate' => $article->created->format('r'),
102                'guid' => $articleUrl,
103                'category' => __('Articles'),
104                'author' => SettingsManager::read('Email.reply_email') . ' (' . $siteName . ')',
105            ];
106        }
107
108        $this->viewBuilder()
109            ->setOption('rootNode', 'rss')
110            ->setOption('serialize', ['@version', 'channel']);
111
112        $this->set([
113            '@version' => '2.0',
114            'channel' => $channelData,
115        ]);
116
117        // Set response type and cache headers
118        $this->response = $this->response
119        ->withType('xml')
120        ->withHeader('Content-Type', 'text/xml; charset=UTF-8')
121        ->withCache('-1 minute', '-1 minute');
122    }
123}