xref: /dokuwiki/inc/Feed/FeedCreator.php (revision 093fe67e98c0cdb4b73fd46938e49b64971483c2)
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        $data = ft_pageSearch($this->options->get('search_query'), $poswords);
151        return array_keys($data);
152    }
153
154    /**
155     * Add recent changed pages to the feed object
156     *
157     * @return array
158     */
159    protected function fetchItemsFromRecentChanges()
160    {
161        global $conf;
162        $flags = 0;
163        if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
164        if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
165        if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
166        if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
167            $flags += RECENTS_MEDIA_CHANGES;
168        }
169        if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
170            $flags += RECENTS_MEDIA_PAGES_MIXED;
171        }
172
173        return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
174    }
175
176    /**
177     * Add items from a plugin to the feed object
178     *
179     * @triggers FEED_MODE_UNKNOWN
180     * @return array
181     */
182    protected function fetchItemsFromPlugin()
183    {
184        $eventData = [
185            'opt' => $this->options->options,
186            'data' => [],
187        ];
188        $event = new Event('FEED_MODE_UNKNOWN', $eventData);
189        if ($event->advise_before(true)) {
190            throw new \RuntimeException('unknown feed mode');
191        }
192        $event->advise_after();
193
194        return $eventData['data'];
195    }
196
197    /**
198     * Add a logo to the feed
199     *
200     * Looks at different possible candidates for a logo and adds the first one
201     *
202     * @return void
203     */
204    protected function initLogo()
205    {
206        global $conf;
207
208        $this->feed->image = new \FeedImage();
209        $this->feed->image->title = $conf['title'];
210        $this->feed->image->link = DOKU_URL;
211        $this->feed->image->url = tpl_getMediaFile([
212            ':wiki:logo.svg',
213            ':logo.svg',
214            ':wiki:logo.png',
215            ':logo.png',
216            ':wiki:logo.jpg',
217            ':logo.jpg',
218            ':wiki:favicon.ico',
219            ':favicon.ico',
220            ':wiki:dokuwiki.svg',
221            ':wiki:dokuwiki-128.png',
222            'images/favicon.ico'
223        ], true);
224    }
225}
226