Completed
Push — sidebaracl ( 7c3e4a...a2e03c )
by Andreas
05:04
created

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * XML feed export
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 *
8
 * @global array $conf
9
 * @global Input $INPUT
10
 */
11
12
if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/');
13
require_once(DOKU_INC.'inc/init.php');
14
15
//close session
16
session_write_close();
17
18
//feed disabled?
19
if(!actionOK('rss')) {
20
    http_status(404);
21
    echo '<error>RSS feed is disabled.</error>';
22
    exit;
23
}
24
25
// get params
26
$opt = rss_parseOptions();
27
28
// the feed is dynamic - we need a cache for each combo
29
// (but most people just use the default feed so it's still effective)
30
$key   = join('', array_values($opt)).'$'.$_SERVER['REMOTE_USER'].'$'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'];
31
$cache = new cache($key, '.feed');
32
33
// prepare cache depends
34
$depends['files'] = getConfigFiles('main');
35
$depends['age']   = $conf['rss_update'];
36
$depends['purge'] = $INPUT->bool('purge');
37
38
// check cacheage and deliver if nothing has changed since last
39
// time or the update interval has not passed, also handles conditional requests
40
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
41
header('Pragma: public');
42
header('Content-Type: application/xml; charset=utf-8');
43
header('X-Robots-Tag: noindex');
44
if($cache->useCache($depends)) {
45
    http_conditionalRequest($cache->_time);
0 ignored issues
show
The property _time cannot be accessed from this context as it is declared private in class cache.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
46
    if($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
47
    print $cache->retrieveCache();
48
    exit;
49
} else {
50
    http_conditionalRequest(time());
51
}
52
53
// create new feed
54
$rss                 = new DokuWikiFeedCreator();
55
$rss->title          = $conf['title'].(($opt['namespace']) ? ' '.$opt['namespace'] : '');
56
$rss->link           = DOKU_URL;
57
$rss->syndicationURL = DOKU_URL.'feed.php';
58
$rss->cssStyleSheet  = DOKU_URL.'lib/exe/css.php?s=feed';
59
60
$image        = new FeedImage();
61
$image->title = $conf['title'];
62
$image->url   = tpl_getMediaFile(array(':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'), true);
63
$image->link  = DOKU_URL;
64
$rss->image   = $image;
65
66
$data  = null;
67
$modes = array(
68
    'list'   => 'rssListNamespace',
69
    'search' => 'rssSearch',
70
    'recent' => 'rssRecentChanges'
71
);
72
if(isset($modes[$opt['feed_mode']])) {
73
    $data = $modes[$opt['feed_mode']]($opt);
74
} else {
75
    $eventData = array(
76
        'opt'  => &$opt,
77
        'data' => &$data,
78
    );
79
    $event     = new Doku_Event('FEED_MODE_UNKNOWN', $eventData);
80
    if($event->advise_before(true)) {
81
        echo sprintf('<error>Unknown feed mode %s</error>', hsc($opt['feed_mode']));
82
        exit;
83
    }
84
    $event->advise_after();
85
}
86
87
rss_buildItems($rss, $data, $opt);
88
$feed = $rss->createFeed($opt['feed_type'], 'utf-8');
89
90
// save cachefile
91
$cache->storeCache($feed);
92
93
// finally deliver
94
print $feed;
95
96
// ---------------------------------------------------------------- //
97
98
/**
99
 * Get URL parameters and config options and return an initialized option array
100
 *
101
 * @author Andreas Gohr <[email protected]>
102
 */
103
function rss_parseOptions() {
104
    global $conf;
105
    global $INPUT;
106
107
    $opt = array();
108
109
    foreach(array(
110
                // Basic feed properties
111
                // Plugins may probably want to add new values to these
112
                // properties for implementing own feeds
113
114
                // One of: list, search, recent
115
                'feed_mode'    => array('str', 'mode', 'recent'),
116
                // One of: diff, page, rev, current
117
                'link_to'      => array('str', 'linkto', $conf['rss_linkto']),
118
                // One of: abstract, diff, htmldiff, html
119
                'item_content' => array('str', 'content', $conf['rss_content']),
120
121
                // Special feed properties
122
                // These are only used by certain feed_modes
123
124
                // String, used for feed title, in list and rc mode
125
                'namespace'    => array('str', 'ns', null),
126
                // Positive integer, only used in rc mode
127
                'items'        => array('int', 'num', $conf['recent']),
128
                // Boolean, only used in rc mode
129
                'show_minor'   => array('bool', 'minor', false),
130
                // String, only used in list mode
131
                'sort'         => array('str', 'sort', 'natural'),
132
                // String, only used in search mode
133
                'search_query' => array('str', 'q', null),
134
                // One of: pages, media, both
135
                'content_type' => array('str', 'view', $conf['rss_media'])
136
137
            ) as $name => $val) {
138
        $opt[$name] = $INPUT->$val[0]($val[1], $val[2], true);
139
    }
140
141
    $opt['items']      = max(0, (int) $opt['items']);
142
    $opt['show_minor'] = (bool) $opt['show_minor'];
143
    $opt['sort'] = valid_input_set('sort', array('default' => 'natural', 'date'), $opt);
144
145
    $opt['guardmail'] = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
146
147
    $type = $INPUT->valid(
148
        'type',
149
        array( 'rss', 'rss2', 'atom', 'atom1', 'rss1'),
150
        $conf['rss_type']
151
    );
152
    switch($type) {
153
        case 'rss':
154
            $opt['feed_type'] = 'RSS0.91';
155
            $opt['mime_type'] = 'text/xml';
156
            break;
157
        case 'rss2':
158
            $opt['feed_type'] = 'RSS2.0';
159
            $opt['mime_type'] = 'text/xml';
160
            break;
161
        case 'atom':
162
            $opt['feed_type'] = 'ATOM0.3';
163
            $opt['mime_type'] = 'application/xml';
164
            break;
165
        case 'atom1':
166
            $opt['feed_type'] = 'ATOM1.0';
167
            $opt['mime_type'] = 'application/atom+xml';
168
            break;
169
        default:
170
            $opt['feed_type'] = 'RSS1.0';
171
            $opt['mime_type'] = 'application/xml';
172
    }
173
174
    $eventData = array(
175
        'opt' => &$opt,
176
    );
177
    trigger_event('FEED_OPTS_POSTPROCESS', $eventData);
178
    return $opt;
179
}
180
181
/**
182
 * Add recent changed pages to a feed object
183
 *
184
 * @author Andreas Gohr <[email protected]>
185
 * @param  FeedCreator $rss the FeedCreator Object
186
 * @param  array       $data the items to add
187
 * @param  array       $opt  the feed options
188
 */
189
function rss_buildItems(&$rss, &$data, $opt) {
190
    global $conf;
191
    global $lang;
192
    /* @var DokuWiki_Auth_Plugin $auth */
193
    global $auth;
194
195
    $eventData = array(
196
        'rss'  => &$rss,
197
        'data' => &$data,
198
        'opt'  => &$opt,
199
    );
200
    $event     = new Doku_Event('FEED_DATA_PROCESS', $eventData);
201
    if($event->advise_before(false)) {
202
        foreach($data as $ditem) {
203
            if(!is_array($ditem)) {
204
                // not an array? then only a list of IDs was given
205
                $ditem = array('id' => $ditem);
206
            }
207
208
            $item = new FeedItem();
209
            $id   = $ditem['id'];
210
            if(!$ditem['media']) {
211
                $meta = p_get_metadata($id);
212
            } else {
213
                $meta = array();
214
            }
215
216
            // add date
217
            if($ditem['date']) {
218
                $date = $ditem['date'];
219
            } elseif ($ditem['media']) {
220
                $date = @filemtime(mediaFN($id));
221
            } elseif (file_exists(wikiFN($id))) {
222
                $date = @filemtime(wikiFN($id));
223
            } elseif($meta['date']['modified']) {
224
                $date = $meta['date']['modified'];
225
            } else {
226
                $date = 0;
227
            }
228
            if($date) $item->date = date('r', $date);
229
230
            // add title
231
            if($conf['useheading'] && $meta['title']) {
232
                $item->title = $meta['title'];
233
            } else {
234
                $item->title = $ditem['id'];
235
            }
236
            if($conf['rss_show_summary'] && !empty($ditem['sum'])) {
237
                $item->title .= ' - '.strip_tags($ditem['sum']);
238
            }
239
240
            // add item link
241
            switch($opt['link_to']) {
242
                case 'page':
243
                    if($ditem['media']) {
244
                        $item->link = media_managerURL(
245
                            array(
246
                                 'image' => $id,
247
                                 'ns'    => getNS($id),
248
                                 'rev'   => $date
249
                            ), '&', true
250
                        );
251
                    } else {
252
                        $item->link = wl($id, 'rev='.$date, true, '&');
253
                    }
254
                    break;
255
                case 'rev':
256
                    if($ditem['media']) {
257
                        $item->link = media_managerURL(
258
                            array(
259
                                 'image'       => $id,
260
                                 'ns'          => getNS($id),
261
                                 'rev'         => $date,
262
                                 'tab_details' => 'history'
263
                            ), '&', true
264
                        );
265
                    } else {
266
                        $item->link = wl($id, 'do=revisions&rev='.$date, true, '&');
267
                    }
268
                    break;
269
                case 'current':
270
                    if($ditem['media']) {
271
                        $item->link = media_managerURL(
272
                            array(
273
                                 'image' => $id,
274
                                 'ns'    => getNS($id)
275
                            ), '&', true
276
                        );
277
                    } else {
278
                        $item->link = wl($id, '', true, '&');
279
                    }
280
                    break;
281
                case 'diff':
282
                default:
283
                    if($ditem['media']) {
284
                        $item->link = media_managerURL(
285
                            array(
286
                                 'image'       => $id,
287
                                 'ns'          => getNS($id),
288
                                 'rev'         => $date,
289
                                 'tab_details' => 'history',
290
                                 'mediado'     => 'diff'
291
                            ), '&', true
292
                        );
293
                    } else {
294
                        $item->link = wl($id, 'rev='.$date.'&do=diff', true, '&');
295
                    }
296
            }
297
298
            // add item content
299
            switch($opt['item_content']) {
300
                case 'diff':
301
                case 'htmldiff':
302
                    if($ditem['media']) {
303
                        $medialog = new MediaChangeLog($id);
304
                        $revs  = $medialog->getRevisions(0, 1);
305
                        $rev   = $revs[0];
306
                        $src_r = '';
307
                        $src_l = '';
308
309
                        if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)), 300)) {
310
                            $more  = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
311
                            $src_r = ml($id, $more, true, '&amp;', true);
312
                        }
313
                        if($rev && $size = media_image_preview_size($id, $rev, new JpegMeta(mediaFN($id, $rev)), 300)) {
314
                            $more  = 'rev='.$rev.'&w='.$size[0].'&h='.$size[1];
315
                            $src_l = ml($id, $more, true, '&amp;', true);
316
                        }
317
                        $content = '';
318
                        if($src_r) {
319
                            $content = '<table>';
320
                            $content .= '<tr><th width="50%">'.$rev.'</th>';
321
                            $content .= '<th width="50%">'.$lang['current'].'</th></tr>';
322
                            $content .= '<tr align="center"><td><img src="'.$src_l.'" alt="" /></td><td>';
323
                            $content .= '<img src="'.$src_r.'" alt="'.$id.'" /></td></tr>';
324
                            $content .= '</table>';
325
                        }
326
327
                    } else {
328
                        require_once(DOKU_INC.'inc/DifferenceEngine.php');
329
                        $pagelog = new PageChangeLog($id);
330
                        $revs = $pagelog->getRevisions(0, 1);
331
                        $rev  = $revs[0];
332
333
                        if($rev) {
334
                            $df = new Diff(explode("\n", rawWiki($id, $rev)),
335
                                           explode("\n", rawWiki($id, '')));
336
                        } else {
337
                            $df = new Diff(array(''),
338
                                           explode("\n", rawWiki($id, '')));
339
                        }
340
341
                        if($opt['item_content'] == 'htmldiff') {
342
                            // note: no need to escape diff output, TableDiffFormatter provides 'safe' html
343
                            $tdf     = new TableDiffFormatter();
344
                            $content = '<table>';
345
                            $content .= '<tr><th colspan="2" width="50%">'.$rev.'</th>';
346
                            $content .= '<th colspan="2" width="50%">'.$lang['current'].'</th></tr>';
347
                            $content .= $tdf->format($df);
348
                            $content .= '</table>';
349
                        } else {
350
                            // note: diff output must be escaped, UnifiedDiffFormatter provides plain text
351
                            $udf     = new UnifiedDiffFormatter();
352
                            $content = "<pre>\n".hsc($udf->format($df))."\n</pre>";
353
                        }
354
                    }
355
                    break;
356
                case 'html':
357
                    if($ditem['media']) {
358
                        if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
359
                            $more    = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
360
                            $src     = ml($id, $more, true, '&amp;', true);
361
                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
362
                        } else {
363
                            $content = '';
364
                        }
365
                    } else {
366
                        if (@filemtime(wikiFN($id)) === $date) {
367
                            $content = p_wiki_xhtml($id, '', false);
368
                        } else {
369
                            $content = p_wiki_xhtml($id, $date, false);
370
                        }
371
                        // no TOC in feeds
372
                        $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s', '', $content);
373
374
                        // add alignment for images
375
                        $content = preg_replace('/(<img .*?class="medialeft")/s', '\\1 align="left"', $content);
376
                        $content = preg_replace('/(<img .*?class="mediaright")/s', '\\1 align="right"', $content);
377
378
                        // make URLs work when canonical is not set, regexp instead of rerendering!
379
                        if(!$conf['canonical']) {
380
                            $base    = preg_quote(DOKU_REL, '/');
381
                            $content = preg_replace('/(<a href|<img src)="('.$base.')/s', '$1="'.DOKU_URL, $content);
382
                        }
383
                    }
384
385
                    break;
386
                case 'abstract':
387
                default:
388
                    if($ditem['media']) {
389
                        if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
390
                            $more    = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
391
                            $src     = ml($id, $more, true, '&amp;', true);
392
                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
393
                        } else {
394
                            $content = '';
395
                        }
396
                    } else {
397
                        $content = $meta['description']['abstract'];
398
                    }
399
            }
400
            $item->description = $content; //FIXME a plugin hook here could be senseful
401
402
            // add user
403
            # FIXME should the user be pulled from metadata as well?
404
            $user         = @$ditem['user']; // the @ spares time repeating lookup
405
            $item->author = '';
406
            if($user && $conf['useacl'] && $auth) {
407
                $userInfo = $auth->getUserData($user);
408
                if($userInfo) {
409
                    switch($conf['showuseras']) {
410
                        case 'username':
411
                        case 'username_link':
412
                            $item->author = $userInfo['name'];
413
                            break;
414
                        default:
415
                            $item->author = $user;
416
                            break;
417
                    }
418
                } else {
419
                    $item->author = $user;
420
                }
421
                if($userInfo && !$opt['guardmail']) {
422
                    $item->authorEmail = $userInfo['mail'];
423
                } else {
424
                    //cannot obfuscate because some RSS readers may check validity
425
                    $item->authorEmail = $user.'@'.$ditem['ip'];
426
                }
427
            } elseif($user) {
428
                // this happens when no ACL but some Apache auth is used
429
                $item->author      = $user;
430
                $item->authorEmail = $user.'@'.$ditem['ip'];
431
            } else {
432
                $item->authorEmail = 'anonymous@'.$ditem['ip'];
433
            }
434
435
            // add category
436
            if(isset($meta['subject'])) {
437
                $item->category = $meta['subject'];
438
            } else {
439
                $cat = getNS($id);
440
                if($cat) $item->category = $cat;
0 ignored issues
show
Bug Best Practice introduced by
The expression $cat of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
441
            }
442
443
            // finally add the item to the feed object, after handing it to registered plugins
444
            $evdata = array(
445
                'item'  => &$item,
446
                'opt'   => &$opt,
447
                'ditem' => &$ditem,
448
                'rss'   => &$rss
449
            );
450
            $evt    = new Doku_Event('FEED_ITEM_ADD', $evdata);
451
            if($evt->advise_before()) {
452
                $rss->addItem($item);
453
            }
454
            $evt->advise_after(); // for completeness
455
        }
456
    }
457
    $event->advise_after();
458
}
459
460
/**
461
 * Add recent changed pages to the feed object
462
 *
463
 * @author Andreas Gohr <[email protected]>
464
 */
465
function rssRecentChanges($opt) {
466
    global $conf;
467
    $flags = RECENTS_SKIP_DELETED;
468
    if(!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS;
469
    if($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_CHANGES;
470
    if($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_PAGES_MIXED;
471
472
    $recents = getRecents(0, $opt['items'], $opt['namespace'], $flags);
473
    return $recents;
474
}
475
476
/**
477
 * Add all pages of a namespace to the feed object
478
 *
479
 * @author Andreas Gohr <[email protected]>
480
 */
481
function rssListNamespace($opt) {
482
    require_once(DOKU_INC.'inc/search.php');
483
    global $conf;
484
485
    $ns = ':'.cleanID($opt['namespace']);
486
    $ns = utf8_encodeFN(str_replace(':', '/', $ns));
487
488
    $data = array();
489
    $search_opts = array(
490
        'depth' => 1,
491
        'pagesonly' => true,
492
        'listfiles' => true
493
    );
494
    search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $opt['sort']);
495
496
    return $data;
497
}
498
499
/**
500
 * Add the result of a full text search to the feed object
501
 *
502
 * @author Andreas Gohr <[email protected]>
503
 */
504
function rssSearch($opt) {
505
    if(!$opt['search_query']) return array();
506
507
    require_once(DOKU_INC.'inc/fulltext.php');
508
    $data = ft_pageSearch($opt['search_query'], $poswords);
509
    $data = array_keys($data);
510
511
    return $data;
512
}
513
514
//Setup VIM: ex: et ts=4 :
515