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