Completed
Push — psr2-plugin ( bf9f56...6f343c )
by Andreas
07:27 queued 03:58
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
use dokuwiki\ChangeLog\MediaChangeLog;
0 ignored issues
show
This use statement conflicts with another class in this namespace, MediaChangeLog.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use dokuwiki\ChangeLog\PageChangeLog;
0 ignored issues
show
This use statement conflicts with another class in this namespace, PageChangeLog.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
14
use dokuwiki\Extension\AuthPlugin;
15
use dokuwiki\Extension\Event;
16
17
if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/');
18
require_once(DOKU_INC.'inc/init.php');
19
20
//close session
21
session_write_close();
22
23
//feed disabled?
24
if(!actionOK('rss')) {
25
    http_status(404);
26
    echo '<error>RSS feed is disabled.</error>';
27
    exit;
28
}
29
30
// get params
31
$opt = rss_parseOptions();
32
33
// the feed is dynamic - we need a cache for each combo
34
// (but most people just use the default feed so it's still effective)
35
$key   = join('', array_values($opt)).'$'.$_SERVER['REMOTE_USER'].'$'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'];
36
$cache = new cache($key, '.feed');
37
38
// prepare cache depends
39
$depends['files'] = getConfigFiles('main');
40
$depends['age']   = $conf['rss_update'];
41
$depends['purge'] = $INPUT->bool('purge');
42
43
// check cacheage and deliver if nothing has changed since last
44
// time or the update interval has not passed, also handles conditional requests
45
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
46
header('Pragma: public');
47
header('Content-Type: application/xml; charset=utf-8');
48
header('X-Robots-Tag: noindex');
49
if($cache->useCache($depends)) {
50
    http_conditionalRequest($cache->_time);
51
    if($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
52
    print $cache->retrieveCache();
53
    exit;
54
} else {
55
    http_conditionalRequest(time());
56
}
57
58
// create new feed
59
$rss                 = new UniversalFeedCreator();
60
$rss->title          = $conf['title'].(($opt['namespace']) ? ' '.$opt['namespace'] : '');
61
$rss->link           = DOKU_URL;
62
$rss->syndicationURL = DOKU_URL.'feed.php';
63
$rss->cssStyleSheet  = DOKU_URL.'lib/exe/css.php?s=feed';
0 ignored issues
show
The property cssStyleSheet does not seem to exist in UniversalFeedCreator.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
64
65
$image        = new FeedImage();
66
$image->title = $conf['title'];
67
$image->url   = tpl_getMediaFile(array(':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'), true);
68
$image->link  = DOKU_URL;
69
$rss->image   = $image;
70
71
$data  = null;
72
$modes = array(
73
    'list'   => 'rssListNamespace',
74
    'search' => 'rssSearch',
75
    'recent' => 'rssRecentChanges'
76
);
77
if(isset($modes[$opt['feed_mode']])) {
78
    $data = $modes[$opt['feed_mode']]($opt);
79
} else {
80
    $eventData = array(
81
        'opt'  => &$opt,
82
        'data' => &$data,
83
    );
84
    $event     = new Event('FEED_MODE_UNKNOWN', $eventData);
85
    if($event->advise_before(true)) {
86
        echo sprintf('<error>Unknown feed mode %s</error>', hsc($opt['feed_mode']));
87
        exit;
88
    }
89
    $event->advise_after();
90
}
91
92
rss_buildItems($rss, $data, $opt);
93
$feed = $rss->createFeed($opt['feed_type']);
0 ignored issues
show
The call to FeedCreator::createFeed() has too many arguments starting with $opt['feed_type'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
94
95
// save cachefile
96
$cache->storeCache($feed);
97
98
// finally deliver
99
print $feed;
100
101
// ---------------------------------------------------------------- //
102
103
/**
104
 * Get URL parameters and config options and return an initialized option array
105
 *
106
 * @author Andreas Gohr <[email protected]>
107
 */
108
function rss_parseOptions() {
109
    global $conf;
110
    global $INPUT;
111
112
    $opt = array();
113
114
    foreach(array(
115
                // Basic feed properties
116
                // Plugins may probably want to add new values to these
117
                // properties for implementing own feeds
118
119
                // One of: list, search, recent
120
                'feed_mode'    => array('str', 'mode', 'recent'),
121
                // One of: diff, page, rev, current
122
                'link_to'      => array('str', 'linkto', $conf['rss_linkto']),
123
                // One of: abstract, diff, htmldiff, html
124
                'item_content' => array('str', 'content', $conf['rss_content']),
125
126
                // Special feed properties
127
                // These are only used by certain feed_modes
128
129
                // String, used for feed title, in list and rc mode
130
                'namespace'    => array('str', 'ns', null),
131
                // Positive integer, only used in rc mode
132
                'items'        => array('int', 'num', $conf['recent']),
133
                // Boolean, only used in rc mode
134
                'show_minor'   => array('bool', 'minor', false),
135
                // String, only used in list mode
136
                'sort'         => array('str', 'sort', 'natural'),
137
                // String, only used in search mode
138
                'search_query' => array('str', 'q', null),
139
                // One of: pages, media, both
140
                'content_type' => array('str', 'view', $conf['rss_media'])
141
142
            ) as $name => $val) {
143
        $opt[$name] = $INPUT->{$val[0]}($val[1], $val[2], true);
144
    }
145
146
    $opt['items']      = max(0, (int) $opt['items']);
147
    $opt['show_minor'] = (bool) $opt['show_minor'];
148
    $opt['sort'] = valid_input_set('sort', array('default' => 'natural', 'date'), $opt);
149
150
    $opt['guardmail'] = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
151
152
    $type = $INPUT->valid(
153
        'type',
154
        array( 'rss', 'rss2', 'atom', 'atom1', 'rss1'),
155
        $conf['rss_type']
156
    );
157
    switch($type) {
158
        case 'rss':
159
            $opt['feed_type'] = 'RSS0.91';
160
            $opt['mime_type'] = 'text/xml';
161
            break;
162
        case 'rss2':
163
            $opt['feed_type'] = 'RSS2.0';
164
            $opt['mime_type'] = 'text/xml';
165
            break;
166
        case 'atom':
167
            $opt['feed_type'] = 'ATOM0.3';
168
            $opt['mime_type'] = 'application/xml';
169
            break;
170
        case 'atom1':
171
            $opt['feed_type'] = 'ATOM1.0';
172
            $opt['mime_type'] = 'application/atom+xml';
173
            break;
174
        default:
175
            $opt['feed_type'] = 'RSS1.0';
176
            $opt['mime_type'] = 'application/xml';
177
    }
178
179
    $eventData = array(
180
        'opt' => &$opt,
181
    );
182
    Event::createAndTrigger('FEED_OPTS_POSTPROCESS', $eventData);
183
    return $opt;
184
}
185
186
/**
187
 * Add recent changed pages to a feed object
188
 *
189
 * @author Andreas Gohr <[email protected]>
190
 * @param  FeedCreator $rss the FeedCreator Object
191
 * @param  array       $data the items to add
192
 * @param  array       $opt  the feed options
193
 */
194
function rss_buildItems(&$rss, &$data, $opt) {
195
    global $conf;
196
    global $lang;
197
    /* @var AuthPlugin $auth */
198
    global $auth;
199
200
    $eventData = array(
201
        'rss'  => &$rss,
202
        'data' => &$data,
203
        'opt'  => &$opt,
204
    );
205
    $event     = new Event('FEED_DATA_PROCESS', $eventData);
206
    if($event->advise_before(false)) {
207
        foreach($data as $ditem) {
208
            if(!is_array($ditem)) {
209
                // not an array? then only a list of IDs was given
210
                $ditem = array('id' => $ditem);
211
            }
212
213
            $item = new FeedItem();
214
            $id   = $ditem['id'];
215
            if(!$ditem['media']) {
216
                $meta = p_get_metadata($id);
217
            } else {
218
                $meta = array();
219
            }
220
221
            // add date
222
            if($ditem['date']) {
223
                $date = $ditem['date'];
224
            } elseif ($ditem['media']) {
225
                $date = @filemtime(mediaFN($id));
226
            } elseif (file_exists(wikiFN($id))) {
227
                $date = @filemtime(wikiFN($id));
228
            } elseif($meta['date']['modified']) {
229
                $date = $meta['date']['modified'];
230
            } else {
231
                $date = 0;
232
            }
233
            if($date) $item->date = date('r', $date);
234
235
            // add title
236
            if($conf['useheading'] && $meta['title']) {
237
                $item->title = $meta['title'];
238
            } else {
239
                $item->title = $ditem['id'];
240
            }
241
            if($conf['rss_show_summary'] && !empty($ditem['sum'])) {
242
                $item->title .= ' - '.strip_tags($ditem['sum']);
243
            }
244
245
            // add item link
246
            switch($opt['link_to']) {
247
                case 'page':
248
                    if($ditem['media']) {
249
                        $item->link = media_managerURL(
250
                            array(
251
                                 'image' => $id,
252
                                 'ns'    => getNS($id),
253
                                 'rev'   => $date
254
                            ), '&', true
255
                        );
256
                    } else {
257
                        $item->link = wl($id, 'rev='.$date, true, '&');
258
                    }
259
                    break;
260
                case 'rev':
261
                    if($ditem['media']) {
262
                        $item->link = media_managerURL(
263
                            array(
264
                                 'image'       => $id,
265
                                 'ns'          => getNS($id),
266
                                 'rev'         => $date,
267
                                 'tab_details' => 'history'
268
                            ), '&', true
269
                        );
270
                    } else {
271
                        $item->link = wl($id, 'do=revisions&rev='.$date, true, '&');
272
                    }
273
                    break;
274
                case 'current':
275
                    if($ditem['media']) {
276
                        $item->link = media_managerURL(
277
                            array(
278
                                 'image' => $id,
279
                                 'ns'    => getNS($id)
280
                            ), '&', true
281
                        );
282
                    } else {
283
                        $item->link = wl($id, '', true, '&');
284
                    }
285
                    break;
286
                case 'diff':
287
                default:
288
                    if($ditem['media']) {
289
                        $item->link = media_managerURL(
290
                            array(
291
                                 'image'       => $id,
292
                                 'ns'          => getNS($id),
293
                                 'rev'         => $date,
294
                                 'tab_details' => 'history',
295
                                 'mediado'     => 'diff'
296
                            ), '&', true
297
                        );
298
                    } else {
299
                        $item->link = wl($id, 'rev='.$date.'&do=diff', true, '&');
300
                    }
301
            }
302
303
            // add item content
304
            switch($opt['item_content']) {
305
                case 'diff':
306
                case 'htmldiff':
307
                    if($ditem['media']) {
308
                        $medialog = new MediaChangeLog($id);
309
                        $revs  = $medialog->getRevisions(0, 1);
310
                        $rev   = $revs[0];
311
                        $src_r = '';
312
                        $src_l = '';
313
314
                        if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)), 300)) {
315
                            $more  = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
316
                            $src_r = ml($id, $more, true, '&amp;', true);
317
                        }
318
                        if($rev && $size = media_image_preview_size($id, $rev, new JpegMeta(mediaFN($id, $rev)), 300)) {
319
                            $more  = 'rev='.$rev.'&w='.$size[0].'&h='.$size[1];
320
                            $src_l = ml($id, $more, true, '&amp;', true);
321
                        }
322
                        $content = '';
323
                        if($src_r) {
324
                            $content = '<table>';
325
                            $content .= '<tr><th width="50%">'.$rev.'</th>';
326
                            $content .= '<th width="50%">'.$lang['current'].'</th></tr>';
327
                            $content .= '<tr align="center"><td><img src="'.$src_l.'" alt="" /></td><td>';
328
                            $content .= '<img src="'.$src_r.'" alt="'.$id.'" /></td></tr>';
329
                            $content .= '</table>';
330
                        }
331
332
                    } else {
333
                        require_once(DOKU_INC.'inc/DifferenceEngine.php');
334
                        $pagelog = new PageChangeLog($id);
335
                        $revs = $pagelog->getRevisions(0, 1);
336
                        $rev  = $revs[0];
337
338
                        if($rev) {
339
                            $df = new Diff(explode("\n", rawWiki($id, $rev)),
340
                                           explode("\n", rawWiki($id, '')));
341
                        } else {
342
                            $df = new Diff(array(''),
343
                                           explode("\n", rawWiki($id, '')));
344
                        }
345
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>';
354
                        } else {
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>";
358
                        }
359
                    }
360
                    break;
361
                case 'html':
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, '&amp;', true);
366
                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
367
                        } else {
368
                            $content = '';
369
                        }
370
                    } else {
371
                        if (@filemtime(wikiFN($id)) === $date) {
372
                            $content = p_wiki_xhtml($id, '', false);
373
                        } else {
374
                            $content = p_wiki_xhtml($id, $date, false);
375
                        }
376
                        // no TOC in feeds
377
                        $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s', '', $content);
378
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);
382
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('/(<a href|<img src)="('.$base.')/s', '$1="'.DOKU_URL, $content);
387
                        }
388
                    }
389
390
                    break;
391
                case 'abstract':
392
                default:
393
                    if($ditem['media']) {
394
                        if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
395
                            $more    = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
396
                            $src     = ml($id, $more, true, '&amp;', true);
397
                            $content = '<img src="'.$src.'" alt="'.$id.'" />';
398
                        } else {
399
                            $content = '';
400
                        }
401
                    } else {
402
                        $content = $meta['description']['abstract'];
403
                    }
404
            }
405
            $item->description = $content; //FIXME a plugin hook here could be senseful
406
407
            // add user
408
            # FIXME should the user be pulled from metadata as well?
409
            $user         = @$ditem['user']; // the @ spares time repeating lookup
410
            if(blank($user)) {
411
                $item->author = 'Anonymous';
412
                $item->authorEmail = '[email protected]';
413
            } else {
414
                $item->author = $user;
415
                $item->authorEmail = $user . '@undisclosed.example.com';
416
417
                // get real user name if configured
418
                if($conf['useacl'] && $auth) {
419
                    $userInfo = $auth->getUserData($user);
420
                    if($userInfo) {
421
                        switch($conf['showuseras']) {
422
                            case 'username':
423
                            case 'username_link':
424
                                $item->author = $userInfo['name'];
425
                                break;
426
                            default:
427
                                $item->author = $user;
428
                                break;
429
                        }
430
                    } else {
431
                        $item->author = $user;
432
                    }
433
                }
434
            }
435
436
            // add category
437
            if(isset($meta['subject'])) {
438
                $item->category = $meta['subject'];
439
            } else {
440
                $cat = getNS($id);
441
                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...
442
            }
443
444
            // finally add the item to the feed object, after handing it to registered plugins
445
            $evdata = array(
446
                'item'  => &$item,
447
                'opt'   => &$opt,
448
                'ditem' => &$ditem,
449
                'rss'   => &$rss
450
            );
451
            $evt    = new Event('FEED_ITEM_ADD', $evdata);
452
            if($evt->advise_before()) {
453
                $rss->addItem($item);
454
            }
455
            $evt->advise_after(); // for completeness
456
        }
457
    }
458
    $event->advise_after();
459
}
460
461
/**
462
 * Add recent changed pages to the feed object
463
 *
464
 * @author Andreas Gohr <[email protected]>
465
 */
466
function rssRecentChanges($opt) {
467
    global $conf;
468
    $flags = RECENTS_SKIP_DELETED;
469
    if(!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS;
470
    if($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_CHANGES;
471
    if($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_PAGES_MIXED;
472
473
    $recents = getRecents(0, $opt['items'], $opt['namespace'], $flags);
474
    return $recents;
475
}
476
477
/**
478
 * Add all pages of a namespace to the feed object
479
 *
480
 * @author Andreas Gohr <[email protected]>
481
 */
482
function rssListNamespace($opt) {
483
    require_once(DOKU_INC.'inc/search.php');
484
    global $conf;
485
486
    $ns = ':'.cleanID($opt['namespace']);
487
    $ns = utf8_encodeFN(str_replace(':', '/', $ns));
488
489
    $data = array();
490
    $search_opts = array(
491
        'depth' => 1,
492
        'pagesonly' => true,
493
        'listfiles' => true
494
    );
495
    search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $opt['sort']);
496
497
    return $data;
498
}
499
500
/**
501
 * Add the result of a full text search to the feed object
502
 *
503
 * @author Andreas Gohr <[email protected]>
504
 */
505
function rssSearch($opt) {
506
    if(!$opt['search_query']) return array();
507
508
    require_once(DOKU_INC.'inc/fulltext.php');
509
    $data = ft_pageSearch($opt['search_query'], $poswords);
510
    $data = array_keys($data);
511
512
    return $data;
513
}
514
515
//Setup VIM: ex: et ts=4 :
516