6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author Andreas Gohr <andi@splitbrain.org>
10 * @global Input $INPUT
13 use dokuwiki\Cache\Cache
;
14 use dokuwiki\ChangeLog\MediaChangeLog
;
15 use dokuwiki\ChangeLog\PageChangeLog
;
16 use dokuwiki\Extension\AuthPlugin
;
17 use dokuwiki\Extension\Event
;
19 if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__
. '/');
20 require_once(DOKU_INC
. 'inc/init.php');
23 session_write_close();
26 if (!actionOK('rss')) {
28 echo '<error>RSS feed is disabled.</error>';
32 $options = new \dokuwiki\Feed\
FeedCreatorOptions();
34 // the feed is dynamic - we need a cache for each combo
35 // (but most people just use the default feed so it's still effective)
37 $options->getCacheKey(),
38 $INPUT->server
->str('REMOTE_USER'),
39 $INPUT->server
->str('HTTP_HOST'),
40 $INPUT->server
->str('SERVER_PORT')
42 $cache = new Cache($key, '.feed');
44 // prepare cache depends
45 $depends['files'] = getConfigFiles('main');
46 $depends['age'] = $conf['rss_update'];
47 $depends['purge'] = $INPUT->bool('purge');
49 // check cacheage and deliver if nothing has changed since last
50 // time or the update interval has not passed, also handles conditional requests
51 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
52 header('Pragma: public');
53 header('Content-Type: ' . $options->get('mime_type'));
54 header('X-Robots-Tag: noindex');
55 if ($cache->useCache($depends)) {
56 http_conditionalRequest($cache->getTime());
57 if ($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
58 echo $cache->retrieveCache();
61 http_conditionalRequest(time());
66 $feed = (new \dokuwiki\Feed\
FeedCreator($options))->build();
67 $cache->storeCache($feed);
69 } catch (Exception
$e) {
71 echo '<error>' . hsc($e->getMessage()) . '</error>';
76 // ---------------------------------------------------------------- //
79 * Get URL parameters and config options and return an initialized option array
81 * @author Andreas Gohr <andi@splitbrain.org>
83 function rss_parseOptions()
92 // Basic feed properties
93 // Plugins may probably want to add new values to these
94 // properties for implementing own feeds
96 // One of: list, search, recent
97 'feed_mode' => ['str', 'mode', 'recent'],
98 // One of: diff, page, rev, current
99 'link_to' => ['str', 'linkto', $conf['rss_linkto']],
100 // One of: abstract, diff, htmldiff, html
101 'item_content' => ['str', 'content', $conf['rss_content']],
103 // Special feed properties
104 // These are only used by certain feed_modes
106 // String, used for feed title, in list and rc mode
107 'namespace' => ['str', 'ns', null],
108 // Positive integer, only used in rc mode
109 'items' => ['int', 'num', $conf['recent']],
110 // Boolean, only used in rc mode
111 'show_minor' => ['bool', 'minor', false],
112 // Boolean, only used in rc mode
113 'only_new' => ['bool', 'onlynewpages', false],
114 // String, only used in list mode
115 'sort' => ['str', 'sort', 'natural'],
116 // String, only used in search mode
117 'search_query' => ['str', 'q', null],
118 // One of: pages, media, both
119 'content_type' => ['str', 'view', $conf['rss_media']]
123 $opt[$name] = $INPUT->{$val[0]}($val[1], $val[2], true);
126 $opt['items'] = max(0, (int)$opt['items']);
127 $opt['show_minor'] = (bool)$opt['show_minor'];
128 $opt['only_new'] = (bool)$opt['only_new'];
129 $opt['sort'] = valid_input_set('sort', ['default' => 'natural', 'date'], $opt);
131 $opt['guardmail'] = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
133 $type = $INPUT->valid(
135 ['rss', 'rss2', 'atom', 'atom1', 'rss1'],
140 $opt['feed_type'] = 'RSS0.91';
141 $opt['mime_type'] = 'text/xml';
144 $opt['feed_type'] = 'RSS2.0';
145 $opt['mime_type'] = 'text/xml';
148 $opt['feed_type'] = 'ATOM0.3';
149 $opt['mime_type'] = 'application/xml';
152 $opt['feed_type'] = 'ATOM1.0';
153 $opt['mime_type'] = 'application/atom+xml';
156 $opt['feed_type'] = 'RSS1.0';
157 $opt['mime_type'] = 'application/xml';
163 Event
::createAndTrigger('FEED_OPTS_POSTPROCESS', $eventData);
168 * Add recent changed pages to a feed object
170 * @param FeedCreator $rss the FeedCreator Object
171 * @param array $data the items to add
172 * @param array $opt the feed options
173 * @author Andreas Gohr <andi@splitbrain.org>
175 function rss_buildItems(&$rss, &$data, $opt)
179 /* @var AuthPlugin $auth */
187 $event = new Event('FEED_DATA_PROCESS', $eventData);
188 if ($event->advise_before(false)) {
189 foreach ($data as $ditem) {
190 if (!is_array($ditem)) {
191 // not an array? then only a list of IDs was given
192 $ditem = ['id' => $ditem];
195 $item = new FeedItem();
197 if (empty($ditem['media'])) {
198 $meta = p_get_metadata($id);
204 if (isset($ditem['date'])) {
205 $date = $ditem['date'];
206 } elseif ($ditem['media']) {
207 $date = @filemtime
(mediaFN($id));
208 } elseif (file_exists(wikiFN($id))) {
209 $date = @filemtime
(wikiFN($id));
210 } elseif ($meta['date']['modified']) {
211 $date = $meta['date']['modified'];
215 if ($date) $item->date
= date('r', $date);
218 if ($conf['useheading'] && $meta['title'] ??
'') {
219 $item->title
= $meta['title'];
221 $item->title
= $ditem['id'];
223 if ($conf['rss_show_summary'] && !empty($ditem['sum'])) {
224 $item->title
.= ' - ' . strip_tags($ditem['sum']);
228 switch ($opt['link_to']) {
230 if (isset($ditem['media'])) {
231 $item->link
= media_managerURL(
241 $item->link
= wl($id, 'rev=' . $date, true, '&');
245 if ($ditem['media']) {
246 $item->link
= media_managerURL(
251 'tab_details' => 'history'
257 $item->link
= wl($id, 'do=revisions&rev=' . $date, true, '&');
261 if ($ditem['media']) {
262 $item->link
= media_managerURL(
271 $item->link
= wl($id, '', true, '&');
276 if ($ditem['media']) {
277 $item->link
= media_managerURL(
282 'tab_details' => 'history',
289 $item->link
= wl($id, 'rev=' . $date . '&do=diff', true, '&');
294 switch ($opt['item_content']) {
297 if ($ditem['media']) {
298 $medialog = new MediaChangeLog($id);
299 $revs = $medialog->getRevisions(0, 1);
304 if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)), 300)) {
305 $more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime
(mediaFN($id));
306 $src_r = ml($id, $more, true, '&', true);
309 $rev && $size = media_image_preview_size(
312 new JpegMeta(mediaFN($id, $rev)),
316 $more = 'rev=' . $rev . '&w=' . $size[0] . '&h=' . $size[1];
317 $src_l = ml($id, $more, true, '&', true);
321 $content = '<table>';
322 $content .= '<tr><th width="50%">' . $rev . '</th>';
323 $content .= '<th width="50%">' . $lang['current'] . '</th></tr>';
324 $content .= '<tr align="center"><td><img src="' . $src_l . '" alt="" /></td><td>';
325 $content .= '<img src="' . $src_r . '" alt="' . $id . '" /></td></tr>';
326 $content .= '</table>';
329 require_once(DOKU_INC
. 'inc/DifferenceEngine.php');
330 $pagelog = new PageChangeLog($id);
331 $revs = $pagelog->getRevisions(0, 1);
336 explode("\n", rawWiki($id, $rev)),
337 explode("\n", rawWiki($id, ''))
342 explode("\n", rawWiki($id, ''))
346 if ($opt['item_content'] == 'htmldiff') {
347 // note: no need to escape diff output, TableDiffFormatter provides 'safe' html
348 $tdf = new TableDiffFormatter();
349 $content = '<table>';
350 $content .= '<tr><th colspan="2" width="50%">' . $rev . '</th>';
351 $content .= '<th colspan="2" width="50%">' . $lang['current'] . '</th></tr>';
352 $content .= $tdf->format($df);
353 $content .= '</table>';
355 // note: diff output must be escaped, UnifiedDiffFormatter provides plain text
356 $udf = new UnifiedDiffFormatter();
357 $content = "<pre>\n" . hsc($udf->format($df)) . "\n</pre>";
362 if ($ditem['media']) {
363 if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
364 $more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime
(mediaFN($id));
365 $src = ml($id, $more, true, '&', true);
366 $content = '<img src="' . $src . '" alt="' . $id . '" />';
371 if (@filemtime
(wikiFN($id)) === $date) {
372 $content = p_wiki_xhtml($id, '', false);
374 $content = p_wiki_xhtml($id, $date, false);
377 $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s', '', $content);
379 // add alignment for images
380 $content = preg_replace('/(<img .*?class="medialeft")/s', '\\1 align="left"', $content);
381 $content = preg_replace('/(<img .*?class="mediaright")/s', '\\1 align="right"', $content);
383 // make URLs work when canonical is not set, regexp instead of rerendering!
384 if (!$conf['canonical']) {
385 $base = preg_quote(DOKU_REL
, '/');
386 $content = preg_replace(
387 '/(<a href|<img src)="(' . $base . ')/s',
397 if (isset($ditem['media'])) {
398 if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
399 $more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime
(mediaFN($id));
400 $src = ml($id, $more, true, '&', true);
401 $content = '<img src="' . $src . '" alt="' . $id . '" />';
406 $content = $meta['description']['abstract'];
409 $item->description
= $content; //FIXME a plugin hook here could be senseful
412 # FIXME should the user be pulled from metadata as well?
413 $user = @$ditem['user']; // the @ spares time repeating lookup
415 $item->author
= 'Anonymous';
416 $item->authorEmail
= 'anonymous@undisclosed.example.com';
418 $item->author
= $user;
419 $item->authorEmail
= $user . '@undisclosed.example.com';
421 // get real user name if configured
422 if ($conf['useacl'] && $auth instanceof AuthPlugin
) {
423 $userInfo = $auth->getUserData($user);
425 switch ($conf['showuseras']) {
427 case 'username_link':
428 $item->author
= $userInfo['name'];
431 $item->author
= $user;
435 $item->author
= $user;
441 if (isset($meta['subject'])) {
442 $item->category
= $meta['subject'];
445 if ($cat) $item->category
= $cat;
448 // finally add the item to the feed object, after handing it to registered plugins
455 $evt = new Event('FEED_ITEM_ADD', $evdata);
456 if ($evt->advise_before()) {
457 $rss->addItem($item);
459 $evt->advise_after(); // for completeness
462 $event->advise_after();
466 * Add recent changed pages to the feed object
468 * @author Andreas Gohr <andi@splitbrain.org>
470 function rssRecentChanges($opt)
474 if (!$conf['rss_show_deleted']) $flags +
= RECENTS_SKIP_DELETED
;
475 if (!$opt['show_minor']) $flags +
= RECENTS_SKIP_MINORS
;
476 if ($opt['only_new']) $flags +
= RECENTS_ONLY_CREATION
;
477 if ($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags +
= RECENTS_MEDIA_CHANGES
;
478 if ($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags +
= RECENTS_MEDIA_PAGES_MIXED
;
480 $recents = getRecents(0, $opt['items'], $opt['namespace'], $flags);
485 * Add all pages of a namespace to the feed object
487 * @author Andreas Gohr <andi@splitbrain.org>
489 function rssListNamespace($opt)
491 require_once(DOKU_INC
. 'inc/search.php');
494 $ns = ':' . cleanID($opt['namespace']);
495 $ns = utf8_encodeFN(str_replace(':', '/', $ns));
503 search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $opt['sort']);
509 * Add the result of a full text search to the feed object
511 * @author Andreas Gohr <andi@splitbrain.org>
513 function rssSearch($opt)
515 if (!$opt['search_query'] ||
!actionOK('search')) return [];
517 require_once(DOKU_INC
. 'inc/fulltext.php');
518 $data = ft_pageSearch($opt['search_query'], $poswords);
519 $data = array_keys($data);
524 //Setup VIM: ex: et ts=4 :