xref: /dokuwiki/inc/Feed/FeedCreator.php (revision 1136941dd74f35b9caa0686cd912ae2847a7c0c7)
1<?php
2
3namespace dokuwiki\Feed;
4
5use dokuwiki\Extension\Event;
6
7class FeedCreator
8{
9
10    /** @var \UniversalFeedCreator */
11    protected $feed;
12
13    /** @var FeedCreatorOptions */
14    protected $options;
15
16    /**
17     * @param FeedCreatorOptions $options
18     */
19    public function __construct(FeedCreatorOptions $options)
20    {
21        $this->options = $options;
22
23        $this->feed = new \UniversalFeedCreator();
24        $this->feed->title = $this->options->get('title');
25        $this->feed->description = $this->options->get('subtitle');
26        $this->feed->link = DOKU_URL;
27        $this->feed->syndicationURL = DOKU_URL . 'feed.php';
28        $this->feed->cssStyleSheet = DOKU_URL . 'lib/exe/css.php?s=feed';
29
30        $this->initLogo();
31    }
32
33    /**
34     * Build the feed
35     *
36     * @return string The raw XML for the feed
37     */
38    public function build()
39    {
40        switch ($this->options->get('feed_mode')) {
41            case 'list':
42                $items = $this->fetchItemsFromNamespace();
43                break;
44            case 'search':
45                $items = $this->fetchItemsFromSearch();
46                break;
47            case 'recent':
48                $items = $this->fetchItemsFromRecentChanges();
49                break;
50            default:
51                $items = $this->fetchItemsFromPlugin();
52        }
53
54        foreach ($items as $item) {
55            $this->createAndAddItem($item);
56        }
57
58        return $this->feed->createFeed($this->options->get('type'));
59    }
60
61    /**
62     * Process the raw data, create feed item and add it to the feed
63     *
64     * @param array|string $data raw item data
65     * @return \FeedItem
66     * @triggers FEED_ITEM_ADD
67     */
68    protected function createAndAddItem($data)
69    {
70        if (is_string($data)) {
71            $data = ['id' => $data];
72        }
73
74        if (($data['mode'] ?? '') == 'media' || isset($data['media'])) {
75            $data['id'] = $data['media'] ?? $data['id'];
76            $proc = new FeedMediaProcessor($data);
77        } else {
78            $proc = new FeedPageProcessor($data);
79        }
80
81        $item = new \FeedItem();
82        $item->title = $proc->getTitle();
83        if ($this->options->get('show_summary') && $proc->getSummary()) {
84            $item->title .= ' - ' . $proc->getSummary();
85        }
86        [$item->authorEmail, $item->author] = $proc->getAuthor();
87        $item->link = $proc->getURL($this->options->get('link_to'));
88        $item->description = $proc->getBody($this->options->get('item_content'));
89
90        $evdata = [
91            'item' => $item,
92            'opt' => &$this->options->options,
93            'ditem' => &$data,
94            'rss' => $this->feed,
95        ];
96
97        $evt = new Event('FEED_ITEM_ADD', $evdata);
98        if ($evt->advise_before()) {
99            $this->feed->addItem($item);
100        }
101        $evt->advise_after();
102
103        return $item;
104    }
105
106    /**
107     * Read all pages from a namespace
108     *
109     * @todo this currently does not honor the rss_media setting and only ever lists pages
110     * @return array
111     */
112    protected function fetchItemsFromNamespace()
113    {
114        global $conf;
115
116        $ns = ':' . cleanID($this->options->get('namespace'));
117        $ns = utf8_encodeFN(str_replace(':', '/', $ns));
118        $data = [];
119        $search_opts = [
120            'depth' => 1,
121            'pagesonly' => true,
122            'listfiles' => true
123        ];
124        search(
125            $data,
126            $conf['datadir'],
127            'search_universal',
128            $search_opts,
129            $ns,
130            $lvl = 1,
131            $this->options->get('sort')
132        );
133
134        return $data;
135    }
136
137    /**
138     * Add the result of a full text search to the feed object
139     *
140     * @return array
141     */
142    protected function fetchItemsFromSearch()
143    {
144        if (!actionOK('search')) throw new \RuntimeException('search is disabled');
145        if (!$this->options->get('search_query')) return [];
146
147        $data = ft_pageSearch($this->options->get('search_query'), $poswords);
148        return array_keys($data);
149    }
150
151    /**
152     * Add recent changed pages to the feed object
153     *
154     * @return array
155     */
156    protected function fetchItemsFromRecentChanges()
157    {
158        global $conf;
159        $flags = 0;
160        if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
161        if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
162        if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
163        if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
164            $flags += RECENTS_MEDIA_CHANGES;
165        }
166        if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
167            $flags += RECENTS_MEDIA_PAGES_MIXED;
168        }
169
170        return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
171    }
172
173    /**
174     * Add items from a plugin to the feed object
175     *
176     * @triggers FEED_MODE_UNKNOWN
177     * @return array
178     */
179    protected function fetchItemsFromPlugin()
180    {
181        $eventData = [
182            'opt' => $this->options->options,
183            'data' => [],
184        ];
185        $event = new Event('FEED_MODE_UNKNOWN', $eventData);
186        if ($event->advise_before(true)) {
187            throw new \RuntimeException('unknown feed mode');
188        }
189        $event->advise_after();
190
191        return $eventData['data'];
192    }
193
194    /**
195     * Add a logo to the feed
196     *
197     * Looks at different possible candidates for a logo and adds the first one
198     *
199     * @return void
200     */
201    protected function initLogo()
202    {
203        global $conf;
204
205        $this->feed->image = new \FeedImage();
206        $this->feed->image->title = $conf['title'];
207        $this->feed->image->link = DOKU_URL;
208        $this->feed->image->url = tpl_getMediaFile([
209            ':wiki:logo.svg',
210            ':logo.svg',
211            ':wiki:logo.png',
212            ':logo.png',
213            ':wiki:logo.jpg',
214            ':logo.jpg',
215            ':wiki:favicon.ico',
216            ':favicon.ico',
217            'images/favicon.ico'
218        ], true);
219    }
220}
221