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