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 9if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/'); 10require_once(DOKU_INC.'inc/init.php'); 11require_once(DOKU_INC.'inc/common.php'); 12require_once(DOKU_INC.'inc/events.php'); 13require_once(DOKU_INC.'inc/parserutils.php'); 14require_once(DOKU_INC.'inc/feedcreator.class.php'); 15require_once(DOKU_INC.'inc/auth.php'); 16require_once(DOKU_INC.'inc/pageutils.php'); 17require_once(DOKU_INC.'inc/httputils.php'); 18 19//close session 20session_write_close(); 21 22// get params 23$opt = rss_parseOptions(); 24 25// the feed is dynamic - we need a cache for each combo 26// (but most people just use the default feed so it's still effective) 27$cache = getCacheName(join('',array_values($opt)).$_SERVER['REMOTE_USER'],'.feed'); 28$key = join('', array_values($opt)) . $_SERVER['REMOTE_USER']; 29$cache = new cache($key, '.feed'); 30 31// prepare cache depends 32$depends['files'] = getConfigFiles('main'); 33$depends['age'] = $conf['rss_update']; 34$depends['purge'] = ($_REQUEST['purge']) ? true : false; 35 36// check cacheage and deliver if nothing has changed since last 37// time or the update interval has not passed, also handles conditional requests 38header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 39header('Pragma: public'); 40header('Content-Type: application/xml; charset=utf-8'); 41header('X-Robots-Tag: noindex'); 42if($cache->useCache($depends)) { 43 http_conditionalRequest($cache->_time); 44 if($conf['allowdebug']) header("X-CacheUsed: $cache->cache"); 45 print $cache->retrieveCache(); 46 exit; 47} else { 48 http_conditionalRequest(time()); 49 } 50 51// create new feed 52$rss = new DokuWikiFeedCreator(); 53$rss->title = $conf['title'].(($opt['namespace']) ? ' '.$opt['namespace'] : ''); 54$rss->link = DOKU_URL; 55$rss->syndicationURL = DOKU_URL.'feed.php'; 56$rss->cssStyleSheet = DOKU_URL.'lib/exe/css.php?s=feed'; 57 58$image = new FeedImage(); 59$image->title = $conf['title']; 60$image->url = DOKU_URL."lib/images/favicon.ico"; 61$image->link = DOKU_URL; 62$rss->image = $image; 63 64$data = null; 65if($opt['feed_mode'] == 'list'){ 66 $data = rssListNamespace($opt); 67}elseif($opt['feed_mode'] == 'search'){ 68 $data = rssSearch($opt); 69}else{ 70 $eventData = array( 71 'opt' => &$opt, 72 'data' => &$data, 73 ); 74 $event = new Doku_Event('FEED_MODE_UNKNOWN', $eventData); 75 if ($event->advise_before(true)) { 76 $data = rssRecentChanges($opt); 77 } 78 $event->advise_after(); 79} 80 81rss_buildItems($rss, $data, $opt); 82$feed = $rss->createFeed($opt['feed_type'],'utf-8'); 83 84// save cachefile 85$cache->storeCache($feed); 86 87// finally deliver 88print $feed; 89 90// ---------------------------------------------------------------- // 91 92/** 93 * Get URL parameters and config options and return a initialized option array 94 * 95 * @author Andreas Gohr <andi@splitbrain.org> 96 */ 97function rss_parseOptions(){ 98 global $conf; 99 100 $opt['items'] = (int) $_REQUEST['num']; 101 $opt['feed_type'] = $_REQUEST['type']; 102 $opt['feed_mode'] = $_REQUEST['mode']; 103 $opt['show_minor'] = $_REQUEST['minor']; 104 $opt['namespace'] = $_REQUEST['ns']; 105 $opt['link_to'] = $_REQUEST['linkto']; 106 $opt['item_content'] = $_REQUEST['content']; 107 $opt['search_query'] = $_REQUEST['q']; 108 109 if(!$opt['feed_type']) $opt['feed_type'] = $conf['rss_type']; 110 if(!$opt['item_content']) $opt['item_content'] = $conf['rss_content']; 111 if(!$opt['link_to']) $opt['link_to'] = $conf['rss_linkto']; 112 if(!$opt['items']) $opt['items'] = $conf['recent']; 113 $opt['guardmail'] = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none'); 114 115 switch ($opt['feed_type']){ 116 case 'rss': 117 $opt['feed_type'] = 'RSS0.91'; 118 $opt['mime_type'] = 'text/xml'; 119 break; 120 case 'rss2': 121 $opt['feed_type'] = 'RSS2.0'; 122 $opt['mime_type'] = 'text/xml'; 123 break; 124 case 'atom': 125 $opt['feed_type'] = 'ATOM0.3'; 126 $opt['mime_type'] = 'application/xml'; 127 break; 128 case 'atom1': 129 $opt['feed_type'] = 'ATOM1.0'; 130 $opt['mime_type'] = 'application/atom+xml'; 131 break; 132 default: 133 $opt['feed_type'] = 'RSS1.0'; 134 $opt['mime_type'] = 'application/xml'; 135 } 136 137 $eventData = array( 138 'opt' => &$opt, 139 ); 140 trigger_event('FEED_OPTS_POSTPROCESS', $eventData); 141 return $opt; 142} 143 144/** 145 * Add recent changed pages to a feed object 146 * 147 * @author Andreas Gohr <andi@splitbrain.org> 148 * @param object $rss - the FeedCreator Object 149 * @param array $data - the items to add 150 * @param array $opt - the feed options 151 */ 152function rss_buildItems(&$rss,&$data,$opt){ 153 global $conf; 154 global $lang; 155 global $auth; 156 157 $eventData = array( 158 'rss' => &$rss, 159 'data' => &$data, 160 'opt' => &$opt, 161 ); 162 $event = new Doku_Event('FEED_DATA_PROCESS', $eventData); 163 if ($event->advise_before(false)){ 164 foreach($data as $ditem){ 165 if(!is_array($ditem)){ 166 // not an array? then only a list of IDs was given 167 $ditem = array( 'id' => $ditem ); 168 } 169 170 $item = new FeedItem(); 171 $id = $ditem['id']; 172 $meta = p_get_metadata($id); 173 174 // add date 175 if($ditem['date']){ 176 $date = $ditem['date']; 177 }elseif($meta['date']['modified']){ 178 $date = $meta['date']['modified']; 179 }else{ 180 $date = @filemtime(wikiFN($id)); 181 } 182 if($date) $item->date = date('r',$date); 183 184 // add title 185 if($conf['useheading'] && $meta['title']){ 186 $item->title = $meta['title']; 187 }else{ 188 $item->title = $ditem['id']; 189 } 190 if($conf['rss_show_summary'] && !empty($ditem['sum'])){ 191 $item->title .= ' - '.strip_tags($ditem['sum']); 192 } 193 194 // add item link 195 switch ($opt['link_to']){ 196 case 'page': 197 $item->link = wl($id,'rev='.$date,true,'&'); 198 break; 199 case 'rev': 200 $item->link = wl($id,'do=revisions&rev='.$date,true,'&'); 201 break; 202 case 'current': 203 $item->link = wl($id, '', true,'&'); 204 break; 205 case 'diff': 206 default: 207 $item->link = wl($id,'rev='.$date.'&do=diff',true,'&'); 208 } 209 210 // add item content 211 switch ($opt['item_content']){ 212 case 'diff': 213 case 'htmldiff': 214 require_once(DOKU_INC.'inc/DifferenceEngine.php'); 215 $revs = getRevisions($id, 0, 1); 216 $rev = $revs[0]; 217 218 if($rev){ 219 $df = new Diff(explode("\n",htmlspecialchars(rawWiki($id,$rev))), 220 explode("\n",htmlspecialchars(rawWiki($id,'')))); 221 }else{ 222 $df = new Diff(array(''), 223 explode("\n",htmlspecialchars(rawWiki($id,'')))); 224 } 225 226 if($opt['item_content'] == 'htmldiff'){ 227 $tdf = new TableDiffFormatter(); 228 $content = '<table>'; 229 $content .= '<tr><th colspan="2" width="50%">'.$rev.'</th>'; 230 $content .= '<th colspan="2" width="50%">'.$lang['current'].'</th></tr>'; 231 $content .= $tdf->format($df); 232 $content .= '</table>'; 233 }else{ 234 $udf = new UnifiedDiffFormatter(); 235 $content = "<pre>\n".$udf->format($df)."\n</pre>"; 236 } 237 break; 238 case 'html': 239 $content = p_wiki_xhtml($id,$date,false); 240 // no TOC in feeds 241 $content = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s','',$content); 242 243 // make URLs work when canonical is not set, regexp instead of rerendering! 244 if(!$conf['canonical']){ 245 $base = preg_quote(DOKU_REL,'/'); 246 $content = preg_replace('/(<a href|<img src)="('.$base.')/s','$1="'.DOKU_URL,$content); 247 } 248 249 break; 250 case 'abstract': 251 default: 252 $content = $meta['description']['abstract']; 253 } 254 $item->description = $content; //FIXME a plugin hook here could be senseful 255 256 // add user 257 # FIXME should the user be pulled from metadata as well? 258 $user = null; 259 $user = @$ditem['user']; // the @ spares time repeating lookup 260 $item->author = ''; 261 if($user && $conf['useacl'] && $auth){ 262 $userInfo = $auth->getUserData($user); 263 $item->author = $userInfo['name']; 264 if($userInfo && !$opt['guardmail']){ 265 $item->authorEmail = $userInfo['mail']; 266 }else{ 267 //cannot obfuscate because some RSS readers may check validity 268 $item->authorEmail = $user.'@'.$recent['ip']; 269 } 270 }elseif($user){ 271 // this happens when no ACL but some Apache auth is used 272 $item->author = $user; 273 $item->authorEmail = $user.'@'.$recent['ip']; 274 }else{ 275 $item->authorEmail = 'anonymous@'.$recent['ip']; 276 } 277 278 // add category 279 if($meta['subject']){ 280 $item->category = $meta['subject']; 281 }else{ 282 $cat = getNS($id); 283 if($cat) $item->category = $cat; 284 } 285 286 // finally add the item to the feed object, after handing it to registered plugins 287 $evdata = array('item' => &$item, 288 'opt' => &$opt, 289 'ditem' => &$ditem, 290 'rss' => &$rss); 291 $evt = new Doku_Event('FEED_ITEM_ADD', $evdata); 292 if ($evt->advise_before()){ 293 $rss->addItem($item); 294 } 295 $evt->advise_after(); // for completeness 296 } 297 } 298 $event->advise_after(); 299} 300 301 302/** 303 * Add recent changed pages to the feed object 304 * 305 * @author Andreas Gohr <andi@splitbrain.org> 306 */ 307function rssRecentChanges($opt){ 308 global $conf; 309 global $auth; 310 311 $flags = RECENTS_SKIP_DELETED; 312 if(!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS; 313 314 $recents = getRecents(0,$opt['items'],$opt['namespace'],$flags); 315 return $recents; 316} 317 318/** 319 * Add all pages of a namespace to the feed object 320 * 321 * @author Andreas Gohr <andi@splitbrain.org> 322 */ 323function rssListNamespace($opt){ 324 require_once(DOKU_INC.'inc/search.php'); 325 global $conf; 326 327 $ns=':'.cleanID($opt['namespace']); 328 $ns=str_replace(':','/',$ns); 329 330 $data = array(); 331 sort($data); 332 search($data,$conf['datadir'],'search_list','',$ns); 333 334 return $data; 335} 336 337/** 338 * Add the result of a full text search to the feed object 339 * 340 * @author Andreas Gohr <andi@splitbrain.org> 341 */ 342function rssSearch($opt){ 343 if(!$opt['search_query']) return; 344 345 require_once(DOKU_INC.'inc/fulltext.php'); 346 $data = array(); 347 $data = ft_pageSearch($opt['search_query'],$poswords); 348 $data = array_keys($data); 349 350 return $data; 351} 352 353//Setup VIM: ex: et ts=4 enc=utf-8 : 354