xref: /dokuwiki/feed.php (revision a5d67245b3fe7279ecdaffc8de36d7d0d9475d97)
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  global $auth;
95
96  if(!$num) $num = $conf['recent'];
97  $guardmail = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
98
99
100  $flags = RECENTS_SKIP_DELETED;
101  if(!$minor) $flags += RECENTS_SKIP_MINORS;
102
103  $recents = getRecents(0,$num,$ns,$flags);
104
105  //this can take some time if a lot of recaching has to be done
106  @set_time_limit(90); // set max execution time
107
108  foreach($recents as $recent){
109
110    $item = new FeedItem();
111    $item->title = $recent['id'];
112    $xhtml = p_wiki_xhtml($recent['id'],'',false);
113
114    if($conf['useheading']) {
115        $matches = array();
116        if(preg_match('|<h([1-9])>(.*?)</h\1>|', $xhtml, $matches))
117            $item->title = trim($matches[2]);
118    }
119    if(!empty($recent['sum'])){
120      $item->title .= ' - '.strip_tags($recent['sum']);
121    }
122
123    $desc = cleanDesc($xhtml);
124
125    if(empty($ltype))
126      $ltype = $conf['rss_linkto'];
127
128    switch ($ltype){
129      case 'page':
130        $item->link = wl($recent['id'],'rev='.$recent['date'],true);
131        break;
132      case 'rev':
133        $item->link = wl($recent['id'],'do=revisions&rev='.$recent['date'],true);
134        break;
135      case 'current':
136        $item->link = wl($recent['id'], '', true);
137        break;
138      case 'diff':
139      default:
140        $item->link = wl($recent['id'],'do=diff'.$recent['date'],true);
141    }
142
143    $item->description = $desc;
144    $item->date        = date('r',$recent['date']);
145    $cat = getNS($recent['id']);
146    if($cat) $item->category = $cat;
147
148    $user = null;
149    $user = @$recent['user']; // the @ spares time repeating lookup
150    $item->author = '';
151
152    if($user){
153      $userInfo = $auth->getUserData($user);
154      $item->author = $userInfo['name'];
155      if($guardmail) {
156        //cannot obfuscate because some RSS readers may check validity
157        $item->authorEmail = $user.'@'.$recent['ip'];
158      }else{
159        $item->authorEmail = $userInfo['mail'];
160      }
161    }else{
162      $item->authorEmail = 'anonymous@'.$recent['ip'];
163    }
164    $rss->addItem($item);
165  }
166}
167
168/**
169 * Add all pages of a namespace to a feedobject
170 *
171 * @author Andreas Gohr <andi@splitbrain.org>
172 */
173function rssListNamespace(&$rss,$ns){
174  require_once(DOKU_INC.'inc/search.php');
175  global $conf;
176
177  $ns=':'.cleanID($ns);
178  $ns=str_replace(':','/',$ns);
179
180  $data = array();
181  sort($data);
182  search($data,$conf['datadir'],'search_list','',$ns);
183  foreach($data as $row){
184    $item = new FeedItem();
185
186    $id    = $row['id'];
187    $date  = filemtime(wikiFN($id));
188    $xhtml = p_wiki_xhtml($id,'',false);
189    $desc  = cleanDesc($xhtml);
190    $item->title       = $id;
191
192    if($conf['useheading']) {
193        $matches = array();
194        if(preg_match('|<h([1-9])>(.*?)</h\1>|', $xhtml, $matches))
195            $item->title = trim($matches[2]);
196    }
197
198    $item->link        = wl($id,'rev='.$date,true);
199    $item->description = $desc;
200    $item->date        = date('r',$date);
201    $rss->addItem($item);
202  }
203}
204
205/**
206 * Clean description for feed inclusion
207 *
208 * Removes HTML tags and line breaks and trims the text to
209 * 250 chars
210 *
211 * @author Andreas Gohr <andi@splitbrain.org>
212 */
213function cleanDesc($desc){
214  //start description at text of first paragraph
215  $matches = array();
216  if(preg_match('/<p>|<p\s.*?>/', $desc, $matches, PREG_OFFSET_CAPTURE))
217      $desc = substr($desc, $matches[0][1]);
218
219  //remove TOC
220  $desc = preg_replace('!<div class="toc">.*?(</div>\n</div>)!s','',$desc);
221  $desc = strip_tags($desc);
222  $desc = preg_replace('/[\n\r\t]/',' ',$desc);
223  $desc = preg_replace('/  /',' ',$desc);
224  $desc = utf8_substr($desc,0,250);
225  $desc = $desc.'...';
226  return $desc;
227}
228
229?>
230