xref: /dokuwiki/inc/Feed/FeedCreator.php (revision e6380ba37d6b3f7dd03146b3c03030ccc8c1b297)
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        switch ($this->options->get('feed_mode')) {
40            case 'list':
41                $items = $this->fetchItemsFromNamespace();
42                break;
43            case 'search':
44                $items = $this->fetchItemsFromSearch();
45                break;
46            case 'recent':
47                $items = $this->fetchItemsFromRecentChanges();
48                break;
49            default:
50                $items = $this->fetchItemsFromPlugin();
51        }
52
53        foreach ($items as $item) {
54            $this->createAndAddItem($item);
55        }
56
57        return $this->feed->createFeed($this->options->get('type'));
58    }
59
60    /**
61     * Process the raw data, create feed item and add it to the feed
62     *
63     * @param array|string $data raw item data
64     * @return \FeedItem
65     * @triggers FEED_ITEM_ADD
66     */
67    protected function createAndAddItem($data)
68    {
69        if (is_string($data)) {
70            $data = ['id' => $data];
71        }
72
73        if (($data['mode'] ?? '') == 'media' || isset($data['media'])) {
74            $data['id'] = $data['media'] ?? $data['id'];
75            $proc = new FeedMediaProcessor($data);
76        } else {
77            $proc = new FeedPageProcessor($data);
78        }
79
80        $item = new \FeedItem();
81        $item->title = $proc->getTitle();
82        if ($this->options->get('show_summary') && $proc->getSummary()) {
83            $item->title .= ' - ' . $proc->getSummary();
84        }
85        $item->date = $proc->getRev();
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
119        $data = [];
120        $search_opts = [
121            'depth' => 1,
122            'pagesonly' => true,
123            'listfiles' => true
124        ];
125        search(
126            $data,
127            $conf['datadir'],
128            'search_universal',
129            $search_opts,
130            $ns,
131            $lvl = 1,
132            $this->options->get('sort')
133        );
134
135        return $data;
136    }
137
138    /**
139     * Add the result of a full text search to the feed object
140     *
141     * @return array
142     */
143    protected function fetchItemsFromSearch()
144    {
145        if (!actionOK('search')) throw new \RuntimeException('search is disabled');
146        if (!$this->options->get('search_query')) return [];
147
148        $data = ft_pageSearch($this->options->get('search_query'), $poswords);
149        return array_keys($data);
150    }
151
152    /**
153     * Add recent changed pages to the feed object
154     *
155     * @return array
156     */
157    protected function fetchItemsFromRecentChanges()
158    {
159        global $conf;
160        $flags = 0;
161        if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
162        if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
163        if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
164        if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
165            $flags += RECENTS_MEDIA_CHANGES;
166        }
167        if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
168            $flags += RECENTS_MEDIA_PAGES_MIXED;
169        }
170
171        return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
172    }
173
174    /**
175     * Add items from a plugin to the feed object
176     *
177     * @triggers FEED_MODE_UNKNOWN
178     * @return array
179     */
180    protected function fetchItemsFromPlugin()
181    {
182        $eventData = [
183            'opt' => $this->options->options,
184            'data' => [],
185        ];
186        $event = new Event('FEED_MODE_UNKNOWN', $eventData);
187        if ($event->advise_before(true)) {
188            throw new \RuntimeException('unknown feed mode');
189        }
190        $event->advise_after();
191
192        return $eventData['data'];
193    }
194
195    /**
196     * Add a logo to the feed
197     *
198     * Looks at different possible candidates for a logo and adds the first one
199     *
200     * @return void
201     */
202    protected function initLogo()
203    {
204        global $conf;
205
206        $this->feed->image = new \FeedImage();
207        $this->feed->image->title = $conf['title'];
208        $this->feed->image->link = DOKU_URL;
209        $this->feed->image->url = tpl_getMediaFile([
210            ':wiki:logo.svg',
211            ':logo.svg',
212            ':wiki:logo.png',
213            ':logo.png',
214            ':wiki:logo.jpg',
215            ':logo.jpg',
216            ':wiki:favicon.ico',
217            ':favicon.ico',
218            ':wiki:dokuwiki.svg',
219            ':wiki:dokuwiki-128.png',
220            'images/favicon.ico'
221        ], true);
222    }
223}
224