xref: /dokuwiki/inc/Feed/FeedCreator.php (revision 2d1b030690419760067aa1b583ec928afef0f5ab)
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        $data = [];
119        $search_opts = [
120            'depth' => 1,
121            'pagesonly' => true,
122            'listfiles' => true
123        ];
124        search(
125            $data,
126            $conf['datadir'],
127            'search_universal',
128            $search_opts,
129            $ns,
130            $lvl = 1,
131            $this->options->get('sort')
132        );
133
134        return $data;
135    }
136
137    /**
138     * Add the result of a full text search to the feed object
139     *
140     * @return array
141     */
142    protected function fetchItemsFromSearch()
143    {
144        if (!actionOK('search')) throw new \RuntimeException('search is disabled');
145        if (!$this->options->get('search_query')) return [];
146
147        $data = ft_pageSearch($this->options->get('search_query'), $poswords);
148        return array_keys($data);
149    }
150
151    /**
152     * Add recent changed pages to the feed object
153     *
154     * @return array
155     */
156    protected function fetchItemsFromRecentChanges()
157    {
158        global $conf;
159        $flags = 0;
160        if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
161        if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
162        if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
163        if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
164            $flags += RECENTS_MEDIA_CHANGES;
165        }
166        if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
167            $flags += RECENTS_MEDIA_PAGES_MIXED;
168        }
169
170        return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
171    }
172
173    /**
174     * Add items from a plugin to the feed object
175     *
176     * @triggers FEED_MODE_UNKNOWN
177     * @return array
178     */
179    protected function fetchItemsFromPlugin()
180    {
181        $eventData = [
182            'opt' => $this->options->options,
183            'data' => [],
184        ];
185        $event = new Event('FEED_MODE_UNKNOWN', $eventData);
186        if ($event->advise_before(true)) {
187            throw new \RuntimeException('unknown feed mode');
188        }
189        $event->advise_after();
190
191        return $eventData['data'];
192    }
193
194    /**
195     * Add a logo to the feed
196     *
197     * Looks at different possible candidates for a logo and adds the first one
198     *
199     * @return void
200     */
201    protected function initLogo()
202    {
203        global $conf;
204
205        $this->feed->image = new \FeedImage();
206        $this->feed->image->title = $conf['title'];
207        $this->feed->image->link = DOKU_URL;
208        $this->feed->image->url = tpl_getMediaFile([
209            ':wiki:logo.svg',
210            ':logo.svg',
211            ':wiki:logo.png',
212            ':logo.png',
213            ':wiki:logo.jpg',
214            ':logo.jpg',
215            ':wiki:favicon.ico',
216            ':favicon.ico',
217            ':wiki:dokuwiki.svg',
218            ':wiki:dokuwiki-128.png',
219            'images/favicon.ico'
220        ], true);
221    }
222}
223