xref: /dokuwiki/inc/Feed/FeedCreator.php (revision a94c17eeed94a74c2fe4e464c1a017be9bbc7157)
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->authorEmail, $item->author] = $proc->getAuthor();
86        $item->link = $proc->getURL($this->options->get('link_to'));
87        $item->description = $proc->getBody($this->options->get('item_content'));
88
89        $evdata = [
90            'item' => $item,
91            'opt' => &$this->options->options,
92            'ditem' => &$data,
93            'rss' => $this->feed,
94        ];
95
96        $evt = new Event('FEED_ITEM_ADD', $evdata);
97        if ($evt->advise_before()) {
98            $this->feed->addItem($item);
99        }
100        $evt->advise_after();
101
102        return $item;
103    }
104
105    /**
106     * Read all pages from a namespace
107     *
108     * @todo this currently does not honor the rss_media setting and only ever lists pages
109     * @return array
110     */
111    protected function fetchItemsFromNamespace()
112    {
113        global $conf;
114
115        $ns = ':' . cleanID($this->options->get('namespace'));
116        $ns = utf8_encodeFN(str_replace(':', '/', $ns));
117        $data = [];
118        $search_opts = [
119            'depth' => 1,
120            'pagesonly' => true,
121            'listfiles' => true
122        ];
123        search(
124            $data,
125            $conf['datadir'],
126            'search_universal',
127            $search_opts,
128            $ns,
129            $lvl = 1,
130            $this->options->get('sort')
131        );
132
133        return $data;
134    }
135
136    /**
137     * Add the result of a full text search to the feed object
138     *
139     * @return array
140     */
141    protected function fetchItemsFromSearch()
142    {
143        if (!actionOK('search')) throw new \RuntimeException('search is disabled');
144        if (!$this->options->get('search_query')) return [];
145
146        $data = ft_pageSearch($this->options->get('search_query'), $poswords);
147        return array_keys($data);
148    }
149
150    /**
151     * Add recent changed pages to the feed object
152     *
153     * @return array
154     */
155    protected function fetchItemsFromRecentChanges()
156    {
157        global $conf;
158        $flags = 0;
159        if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
160        if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
161        if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
162        if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
163            $flags += RECENTS_MEDIA_CHANGES;
164        }
165        if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
166            $flags += RECENTS_MEDIA_PAGES_MIXED;
167        }
168
169        return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
170    }
171
172    /**
173     * Add items from a plugin to the feed object
174     *
175     * @triggers FEED_MODE_UNKNOWN
176     * @return array
177     */
178    protected function fetchItemsFromPlugin()
179    {
180        $eventData = [
181            'opt' => $this->options->options,
182            'data' => [],
183        ];
184        $event = new Event('FEED_MODE_UNKNOWN', $eventData);
185        if ($event->advise_before(true)) {
186            throw new \RuntimeException('unknown feed mode');
187        }
188        $event->advise_after();
189
190        return $eventData['data'];
191    }
192
193    /**
194     * Add a logo to the feed
195     *
196     * Looks at different possible candidates for a logo and adds the first one
197     *
198     * @return void
199     */
200    protected function initLogo()
201    {
202        global $conf;
203
204        $this->feed->image = new \FeedImage();
205        $this->feed->image->title = $conf['title'];
206        $this->feed->image->link = DOKU_URL;
207        $this->feed->image->url = tpl_getMediaFile([
208            ':wiki:logo.svg',
209            ':logo.svg',
210            ':wiki:logo.png',
211            ':logo.png',
212            ':wiki:logo.jpg',
213            ':logo.jpg',
214            ':wiki:favicon.ico',
215            ':favicon.ico',
216            'images/favicon.ico'
217        ], true);
218    }
219}
220