xref: /dokuwiki/feed.php (revision 1d3e0272316696a8def74759b5e061d6b8387b5f)
1<?php
2/**
3 * XML feed export
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <andi@splitbrain.org>
7 */
8
9  if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__)).'/');
10  require_once(DOKU_INC.'inc/init.php');
11  require_once(DOKU_INC.'inc/common.php');
12  require_once(DOKU_INC.'inc/parserutils.php');
13  require_once(DOKU_INC.'inc/feedcreator.class.php');
14  require_once(DOKU_INC.'inc/auth.php');
15
16  //close session
17  session_write_close();
18
19
20  $num   = $_REQUEST['num'];
21  $type  = $_REQUEST['type'];
22  $mode  = $_REQUEST['mode'];
23  $minor = $_REQUEST['minor'];
24  $ns    = $_REQUEST['ns'];
25  $ltype = $_REQUEST['linkto'];
26
27  if($type == '')
28    $type = $conf['rss_type'];
29
30  switch ($type){
31    case 'rss':
32       $type = 'RSS0.9';
33       break;
34    case 'rss2':
35       $type = 'RSS2.0';
36       break;
37    case 'atom':
38       $type = 'ATOM0.3';
39       break;
40    default:
41       $type = 'RSS1.0';
42  }
43
44	// the feed is dynamic - we need a cache for each combo
45  // (but most people just use the default feed so it's still effective)
46	$cache = getCacheName($num.$type.$mode.$ns.$ltype.$_SERVER['REMOTE_USER'],'.feed');
47
48	// check cacheage and deliver if nothing has changed since last
49  // time (with 5 minutes settletime)
50	$cmod = @filemtime($cache); // 0 if not exists
51	if($cmod && ($cmod+(5*60) >= @filemtime($conf['changelog']))){
52  	header('Content-Type: application/xml; charset=utf-8');
53		print io_readFile($cache);
54		exit;
55  }
56
57	// create new feed
58  $rss = new DokuWikiFeedCreator();
59  $rss->title = $conf['title'].(($ns) ? ' '.$ns : '');
60  $rss->link  = DOKU_URL;
61  $rss->syndicationURL = DOKU_URL.'feed.php';
62  $rss->cssStyleSheet  = DOKU_URL.'lib/styles/feed.css';
63
64  $image = new FeedImage();
65  $image->title = $conf['title'];
66  $image->url = DOKU_URL."lib/images/favicon.ico";
67  $image->link = DOKU_URL;
68  $rss->image = $image;
69
70  if($mode == 'list'){
71    rssListNamespace($rss,$ns);
72  }else{
73    rssRecentChanges($rss,$num,$ltype,$ns,$minor);
74  }
75
76  $feed = $rss->createFeed($type,'utf-8');
77
78  // save cachefile
79	io_saveFile($cache,$feed);
80
81	// finally deliver
82  header('Content-Type: application/xml; charset=utf-8');
83  print $feed;
84
85// ---------------------------------------------------------------- //
86
87/**
88 * Add recent changed to a feed object
89 *
90 * @author Andreas Gohr <andi@splitbrain.org>
91 */
92function rssRecentChanges(&$rss,$num,$ltype,$ns,$minor){
93  global $conf;
94  if(!$num) $num = $conf['recent'];
95  $guardmail = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
96
97
98	$flags = RECENTS_SKIP_DELETED;
99  if(!$minor) $flags += RECENTS_SKIP_MINORS;
100
101  $recents = getRecents(0,$num,$ns,$flags);
102
103  //this can take some time if a lot of recaching has to be done
104  @set_time_limit(90); // set max execution time
105
106	foreach($recents as $recent){
107
108    $item = new FeedItem();
109    $item->title = $recent['id'];
110    $xhtml = p_wiki_xhtml($recent['id'],'',false);
111
112    if($conf['useheading']) {
113        $matches = array();
114        if(preg_match('|<h([1-9])>(.*?)</h\1>|', $xhtml, $matches))
115            $item->title = trim($matches[2]);
116    }
117    if(!empty($recent['sum'])){
118      $item->title .= ' - '.strip_tags($recent['sum']);
119    }
120
121    $desc = cleanDesc($xhtml);
122
123    if(empty($ltype))
124      $ltype = $conf['rss_linkto'];
125
126    switch ($ltype){
127      case 'page':
128        $item->link = wl($recent['id'],'rev='.$recent['date'],true);
129        break;
130      case 'rev':
131        $item->link = wl($recent['id'],'do=revisions&rev='.$recent['date'],true);
132        break;
133      case 'current':
134        $item->link = wl($recent['id'], '', true);
135        break;
136      case 'diff':
137      default:
138        $item->link = wl($recent['id'],'do=diff'.$recent['date'],true);
139    }
140
141    $item->description = $desc;
142    $item->date        = date('r',$recent['date']);
143		$cat = getNS($recent['id']);
144		if($cat) $item->category = $cat;
145
146    $user = null;
147    $user = @$recent['user']; // the @ spares time repeating lookup
148    $item->author = '';
149
150    if($user){
151      $userInfo = auth_getUserData($user);
152      $item->author = $userInfo['name'];
153      if($guardmail) {
154        //cannot obfuscate because some RSS readers may check validity
155        $item->authorEmail = $user.'@'.$recent['ip'];
156      }else{
157        $item->authorEmail = $userInfo['mail'];
158      }
159    }else{
160      $item->authorEmail = 'anonymous@'.$recent['ip'];
161    }
162    $rss->addItem($item);
163  }
164}
165
166/**
167 * Add all pages of a namespace to a feedobject
168 *
169 * @author Andreas Gohr <andi@splitbrain.org>
170 */
171function rssListNamespace(&$rss,$ns){
172  require_once(DOKU_INC.'inc/search.php');
173  global $conf;
174
175  $ns=':'.cleanID($ns);
176  $ns=str_replace(':','/',$ns);
177
178  $data = array();
179  sort($data);
180  search($data,$conf['datadir'],'search_list','',$ns);
181  foreach($data as $row){
182    $item = new FeedItem();
183
184    $id    = $row['id'];
185    $date  = filemtime(wikiFN($id));
186    $xhtml = p_wiki_xhtml($id,'',false);
187    $desc  = cleanDesc($xhtml);
188    $item->title       = $id;
189
190    if($conf['useheading']) {
191        $matches = array();
192        if(preg_match('|<h([1-9])>(.*?)</h\1>|', $xhtml, $matches))
193            $item->title = trim($matches[2]);
194    }
195
196    $item->link        = wl($id,'rev='.$date,true);
197    $item->description = $desc;
198    $item->date        = date('r',$date);
199    $rss->addItem($item);
200  }
201}
202
203/**
204 * Clean description for feed inclusion
205 *
206 * Removes HTML tags and line breaks and trims the text to
207 * 250 chars
208 *
209 * @author Andreas Gohr <andi@splitbrain.org>
210 */
211function cleanDesc($desc){
212  //start description at text of first paragraph
213  $matches = array();
214  if(preg_match('/<p>|<p\s.*?>/', $desc, $matches, PREG_OFFSET_CAPTURE))
215      $desc = substr($desc, $matches[0][1]);
216
217  //remove TOC
218  $desc = preg_replace('!<div class="toc">.*?(</div>\n</div>)!s','',$desc);
219  $desc = strip_tags($desc);
220  $desc = preg_replace('/[\n\r\t]/',' ',$desc);
221  $desc = preg_replace('/  /',' ',$desc);
222  $desc = utf8_substr($desc,0,250);
223  $desc = $desc.'...';
224  return $desc;
225}
226
227?>
228