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