1<?php
2  /**
3   * Renderer for nroff/man output
4   *
5   * @author Harry Fuecks <hfuecks@gmail.com>
6   * @author Andreas Gohr <andi@splitbrain.org>
7   */
8
9if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../../').'/');
10
11if ( !defined('DOKU_LF') ) {
12	// Some whitespace to help View > Source
13	define ('DOKU_LF',"\n");
14}
15
16if ( !defined('DOKU_TAB') ) {
17	// Some whitespace to help View > Source
18	define ('DOKU_TAB',"\t");
19}
20
21require_once DOKU_INC . 'inc/parser/renderer.php';
22///require_once DOKU_INC . 'inc/html.php';
23
24/**
25 * The Renderer
26 */
27class renderer_plugin_nroff extends Doku_Renderer {
28
29	// @access public
30	var $doc = '';        // will contain the whole document
31	var $toc = array();   // will contain the Table of Contents
32
33
34	var $headers = array();
35	var $footnotes = array();
36	var $lastsec = 0;
37	var $store = '';
38
39    /**
40     * return some info
41     */
42	function getInfo(){
43		return confToHash(dirname(__FILE__).'/info.txt');
44	}
45
46	function getFormat(){
47		return 'nroff';
48	}
49
50
51	function document_start() {
52		//reset some internals
53		$this->toc     = array();
54		$this->headers = array();
55	}
56
57	function document_end() {
58		global $INFO;
59		global $conf;
60//		/*if ( count ($this->footnotes) > 0 ) {
61//		 $this->doc .= '<div class="footnotes">'.DOKU_LF;
62//
63//		 $id = 0;
64//		 foreach ( $this->footnotes as $footnote ) {
65//		 $id++;   // the number of the current footnote
66//
67//		 // check its not a placeholder that indicates actual footnote text is elsewhere
68//		 if (substr($footnote, 0, 5) != "@@FNT") {
69//
70//		 // open the footnote and set the anchor and backlink
71//		 $this->doc .= '<div class="fn">';
72//		 $this->doc .= '<a href="#fnt__'.$id.'" id="fn__'.$id.'" name="fn__'.$id.'" class="fn_bot">';
73//		 $this->doc .= $id.')</a> '.DOKU_LF;
74//
75//		 // get any other footnotes that use the same markup
76//		 $alt = array_keys($this->footnotes, "@@FNT$id");
77//
78//		 if (count($alt)) {
79//		 foreach ($alt as $ref) {
80//		 // set anchor and backlink for the other footnotes
81//		 $this->doc .= ', <a href="#fnt__'.($ref+1).'" id="fn__'.($ref+1).'" name="fn__'.($ref+1).'" class="fn_bot">';
82//		 $this->doc .= ($ref+1).')</a> '.DOKU_LF;
83//		 }
84//		 }
85//
86//		 // add footnote markup and close this footnote
87//		 $this->doc .= $footnote;
88//		 $this->doc .= '</div>' . DOKU_LF;
89//		 }
90//		 }
91//		 $this->doc .= '</div>'.DOKU_LF;
92//		 }*/
93
94		// Prepare the TOC
95		if(is_array($this->toc) && count($this->toc) > 2){
96///            $TOC = $this->toc;
97//            global $TOC;
98//            $TOC = array($this->toc);
99		}
100
101		// make sure there are no empty paragraphs
102		// get the manpage section from the query string
103		$section = 0;
104		if (isset($_GET['section']) && is_numeric($_GET['section']))
105			$section = $_GET['section'];
106		$date = @date('Y-n-j',$INFO['lastmod']);
107		/* we need to undo the work of lib/plugins/acl/script.js */
108		$this->doc = str_replace("&#039;", "'", $this->doc);
109		$this->doc = str_replace("&lt;",   "<", $this->doc);
110		$this->doc = str_replace("&gt;",   ">", $this->doc);
111		$this->doc = str_replace("&amp;",  "&", $this->doc);
112		$this->doc = str_replace("-",    '\\-', $this->doc);
113
114		$this->doc = '.TH '.
115			strtoupper($this->toc[0]).' '.
116			$section.' '.
117			$date.' '.
118			$this->toc[0].' '.
119			$conf['title'].DOKU_LF.$this->doc;
120	}
121
122	function toc_additem($id, $text, $level) {
123		global $conf;
124
125		//handle TOC
126		if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){
127			$this->toc[] =  $text;
128		}
129	}
130
131	function header($text, $level, $pos) {
132
133		$hid = $this->_headerToLink($text,true);
134
135		//only add items within configured levels
136		$this->toc_additem($hid, $text, $level);
137
138		// write the header
139		if ($level < 2) {
140			$this->doc .= DOKU_LF.'.SH ';
141			$this->doc .= $this->_xmlEntities($text);
142			$this->doc .= DOKU_LF;
143		}
144		else {
145			$this->doc .= DOKU_LF.'.Ss ';
146			$this->doc .= $this->_xmlEntities($text);
147			$this->doc .= DOKU_LF;
148		}
149
150	}
151
152
153	/**
154	 * Section edit marker is replaced by an edit button when
155	 * the page is editable. Replacement done in inc/html.php html_secedit
156	 * @author Andreas Gohr <andi@splitbrain.org>
157	 * @author Ben Coburn   <btcoburn@silicodon.net>
158	 */
159	function section_edit($start, $end, $level, $name) {
160		global $conf;
161
162		if ($start!=-1 && $level<=$conf['maxseclevel']) {
163			$name = str_replace('"', '', $name);
164			///$this->doc .= '<!-- SECTION "'.$name.'" ['.$start.'-'.(($end===0)?'':$end).'] -->';
165		}
166	}
167
168	function section_open($level) {
169//        $this->doc .= "<div class=\"level$level\">".DOKU_LF;
170	}
171
172	function section_close() {
173//        $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
174	}
175
176	function cdata($text) {
177		if ($this->strong)
178			$this->strongbuff .= $this->_xmlEntities($text);
179		else
180			$this->doc .= $this->_xmlEntities($text);
181	}
182
183	function p_open() {
184//        $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
185	}
186
187	function p_close() {
188//        $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
189	}
190
191	function linebreak() {
192		$this->doc .= DOKU_LF.'.BR ';
193	}
194
195	function hr() {
196		$this->doc .= '<hr />'.DOKU_LF;
197	}
198
199	function strong_open() {
200		$this->strong = TRUE;
201		$this->strongbuff = "";
202	}
203
204	function strong_close() {
205		$this->strong = FALSE;
206		$this->doc .= '\\fB'
207			.preg_replace('# #','\\fR \\fB',$this->strongbuff)
208			.'\\fR';
209	}
210
211	function emphasis_open() {
212//        $this->doc .= '<em>';
213	}
214
215	function emphasis_close() {
216		//       $this->doc .= '</em>';
217	}
218
219	function underline_open() {
220		$this->doc .= '\fI';
221	}
222
223	function underline_close() {
224		$this->doc .= '\fP/';
225	}
226
227	function monospace_open() {
228		$this->doc .= DOKU_LF.'.nf'.DOKU_LF;
229	}
230
231	function monospace_close() {
232		$this->doc .= DOKU_LF.'.fi'.DOKU_LF;
233	}
234
235	function subscript_open() {
236		$this->doc .= '<sub>';
237	}
238
239	function subscript_close() {
240		$this->doc .= '</sub>';
241	}
242
243	function superscript_open() {
244		$this->doc .= '<sup>';
245	}
246
247	function superscript_close() {
248		$this->doc .= '</sup>';
249	}
250
251	function deleted_open() {
252		$this->doc .= '<del>';
253	}
254
255	function deleted_close() {
256		$this->doc .= '</del>';
257	}
258
259	/**
260	 * Callback for footnote start syntax
261	 *
262	 * All following content will go to the footnote instead of
263	 * the document. To achieve this the previous rendered content
264	 * is moved to $store and $doc is cleared
265	 *
266	 * @author Andreas Gohr <andi@splitbrain.org>
267	 */
268	function footnote_open() {
269
270		// move current content to store and record footnote
271		$this->store = $this->doc;
272		$this->doc   = '';
273	}
274
275	/**
276	 * Callback for footnote end syntax
277	 *
278	 * All rendered content is moved to the $footnotes array and the old
279	 * content is restored from $store again
280	 *
281	 * @author Andreas Gohr
282	 */
283	function footnote_close() {
284
285		// recover footnote into the stack and restore old content
286		$footnote = $this->doc;
287		$this->doc = $this->store;
288		$this->store = '';
289
290		// check to see if this footnote has been seen before
291		$i = array_search($footnote, $this->footnotes);
292
293		if ($i === false) {
294			// its a new footnote, add it to the $footnotes array
295			$id = count($this->footnotes)+1;
296			$this->footnotes[count($this->footnotes)] = $footnote;
297		} else {
298			// seen this one before, translate the index to an id and save a placeholder
299			$i++;
300			$id = count($this->footnotes)+1;
301			$this->footnotes[count($this->footnotes)] = "@@FNT".($i);
302		}
303
304		// output the footnote reference and link
305		$this->doc .= '<a href="#fn__'.$id.'" name="fnt__'.$id.'" id="fnt__'.$id.'" class="fn_top">'.$id.')</a>';
306	}
307
308	function listu_open() {
309		$this->doc .= DOKU_LF.'*'.DOKU_LF;
310	}
311
312	function listu_close() {
313//        $this->doc .= '</ul>'.DOKU_LF;
314	}
315
316	function listo_open() {
317		$this->doc .= '<ol>'.DOKU_LF;
318	}
319
320	function listo_close() {
321		$this->doc .= '</ol>'.DOKU_LF;
322	}
323
324	function listitem_open($level) {
325		$this->doc .= '<li class="level'.$level.'">';
326	}
327
328	function listitem_close() {
329		$this->doc .= '</li>'.DOKU_LF;
330	}
331
332	function listcontent_open() {
333		$this->doc .= '<div class="li">';
334	}
335
336	function listcontent_close() {
337		$this->doc .= '</div>'.DOKU_LF;
338	}
339
340	function unformatted($text) {
341		$this->doc .= $this->_xmlEntities($text);
342	}
343
344	/**
345	 * Execute PHP code if allowed
346	 *
347	 * @author Andreas Gohr <andi@splitbrain.org>
348	 */
349	function php($text) {
350		ob_start();
351		eval($text);
352		$this->doc .= ob_get_contents();
353		ob_end_clean();
354	}
355
356	function phpblock($text) {
357		$this->php($text);
358	}
359
360	/**
361	 * Insert HTML if allowed
362	 *
363	 * @author Andreas Gohr <andi@splitbrain.org>
364	 */
365	function html($text) {
366		$this->doc .= $text;
367	}
368
369	function htmlblock($text) {
370		$this->html($text);
371	}
372
373	function preformatted($text) {
374		$this->doc .= DOKU_LF.'.nf'.DOKU_LF. $text .DOKU_LF.'.fi'. DOKU_LF;
375	}
376
377	function file($text) {
378		$this->doc .= '<pre class="file">' . $this->_xmlEntities($text). '</pre>'. DOKU_LF;
379	}
380
381	function quote_open() {
382		$this->doc .= '<blockquote><div class="no">'.DOKU_LF;
383	}
384
385	function quote_close() {
386		$this->doc .= '</div></blockquote>'.DOKU_LF;
387	}
388
389	/**
390	 * Callback for code text
391	 *
392	 * Uses GeSHi to highlight language syntax
393	 *
394	 * @author Andreas Gohr <andi@splitbrain.org>
395	 */
396	function code($text, $language = NULL) {
397		global $conf;
398
399		if ( is_null($language) ) {
400			$this->preformatted($text);
401		} else {
402			//strip leading and trailing blank line
403			$text = preg_replace('/^\s*?\n/','',$text);
404			$text = preg_replace('/\s*?\n$/','',$text);
405			$this->doc .= p_xhtml_cached_geshi($text, $language);
406		}
407	}
408
409	function acronym($acronym) {
410		$this->doc .= $acronym;
411		/* hm, hno acronyms for manpages.
412		 if ( array_key_exists($acronym, $this->acronyms) ) {
413
414		 $title = $this->_xmlEntities($this->acronyms[$acronym]);
415
416		 $this->doc .= '<acronym title="'.$title
417		 .'">'.$this->_xmlEntities($acronym).'</acronym>';
418
419		 } else {
420		 $this->doc .= $this->_xmlEntities($acronym);
421		 }
422		*/
423	}
424
425	function smiley($smiley) {
426		/* hm, no smileys in manpages. */
427	}
428
429	/*
430	 * not used
431	 function wordblock($word) {
432	 if ( array_key_exists($word, $this->badwords) ) {
433	 $this->doc .= '** BLEEP **';
434	 } else {
435	 $this->doc .= $this->_xmlEntities($word);
436	 }
437	 }
438	*/
439
440	function entity($entity) {
441		$this->doc .= '->>>>'.$entity.'<<<<<<<-';
442	}
443
444/*
445 function entity($entity) {
446 if ( array_key_exists($entity, $this->entities) ) {
447 $this->doc .= $this->entities[$entity];
448 } else {
449 $this->doc .= $this->_xmlEntities($entity);
450 }
451 }
452*/
453	function multiplyentity($x, $y) {
454		$this->doc .= '->>>>'.$entity.'<<<<<<<-';
455//        $this->doc .= "$x&times;$y";
456	}
457
458	function singlequoteopening() {
459		$this->doc .= '->>>>\'<<<<<<<-';
460///        global $lang;
461//        $this->doc .= $lang['singlequoteopening'];
462	}
463
464	function singlequoteclosing() {
465		$this->doc .= '->>>>\'<<<<<<<-';
466//        global $lang;
467//        $this->doc .= $lang['singlequoteclosing'];
468	}
469
470	function apostrophe() {
471//        global $lang;
472		$this->doc .= '->>>>\'<<<<<<<-';
473//        $this->doc .= $lang['apostrophe'];
474	}
475
476	function doublequoteopening() {
477//        global $lang;
478		$this->doc .= '"';
479//        $this->doc .= $lang['doublequoteopening'];
480	}
481
482	function doublequoteclosing() {
483//        global $lang;
484		$this->doc .= '"';
485//        $this->doc .= $lang['doublequoteclosing'];
486	}
487
488	/**
489	 */
490	function camelcaselink($link) {
491		$this->internallink($link,$link);
492	}
493
494
495	function locallink($hash, $name = NULL){
496		global $ID;
497		$name  = $this->_getLinkTitle($name, $hash, $isImage);
498		$hash  = $this->_headerToLink($hash);
499		$title = $ID.' &crarr;';
500		$this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
501		$this->doc .= $name;
502		$this->doc .= '</a>';
503	}
504
505	/**
506	 * Render an internal Wiki Link
507	 *
508	 * $search and $returnonly are not for the renderer but are used
509	 * elsewhere - no need to implement them in other renderers
510	 *
511	 * @author Andreas Gohr <andi@splitbrain.org>
512	 */
513	function internallink($id, $name = NULL, $search=NULL,$returnonly=false) {
514		global $conf;
515		global $ID;
516		// default name is based on $id as given
517		$default = $this->_simpleTitle($id);
518
519		// now first resolve and clean up the $id
520		resolve_pageid(getNS($ID),$id,$exists);
521		$name = $this->_getLinkTitle($name, $default, $isImage, $id);
522		if ( !$isImage ) {
523			if ( $exists ) {
524				$class='wikilink1';
525			} else {
526				$class='wikilink2';
527			}
528		} else {
529			$class='media';
530		}
531
532		//keep hash anchor
533		list($id,$hash) = explode('#',$id,2);
534		if(!empty($hash)) $hash = $this->_headerToLink($hash);
535
536		//prepare for formating
537		$link['target'] = $conf['target']['wiki'];
538		$link['style']  = '';
539		$link['pre']    = '';
540		$link['suf']    = '';
541		// highlight link to current page
542		if ($id == $ID) {
543			$link['pre']    = '<span class="curid">';
544			$link['suf']    = '</span>';
545		}
546		$link['more']   = '';
547		$link['class']  = $class;
548		$link['url']    = wl($id);
549		$link['name']   = $name;
550		$link['title']  = $id;
551		//add search string
552		if($search){
553			($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&amp;s=';
554			$link['url'] .= rawurlencode($search);
555		}
556
557		//keep hash
558		if($hash) $link['url'].='#'.$hash;
559
560		//output formatted
561		if($returnonly){
562			return $this->_formatLink($link);
563		}else{
564			$this->doc .= $this->_formatLink($link);
565		}
566	}
567
568	function externallink($url, $name = NULL) {
569		global $conf;
570
571		$name = $this->_getLinkTitle($name, $url, $isImage);
572
573		if ( !$isImage ) {
574			$class='urlextern';
575		} else {
576			$class='media';
577		}
578
579		//prepare for formating
580		$link['target'] = $conf['target']['extern'];
581		$link['style']  = '';
582		$link['pre']    = '';
583		$link['suf']    = '';
584		$link['more']   = '';
585		$link['class']  = $class;
586		$link['url']    = $url;
587
588		$link['name']   = $name;
589		$link['title']  = $this->_xmlEntities($url);
590		if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
591
592		//output formatted
593		$this->doc .= $this->_formatLink($link);
594	}
595
596	/**
597	 */
598	function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
599		global $conf;
600
601		$link = array();
602		$link['target'] = $conf['target']['interwiki'];
603		$link['pre']    = '';
604		$link['suf']    = '';
605		$link['more']   = '';
606		$link['name']   = $this->_getLinkTitle($name, $wikiUri, $isImage);
607
608		//get interwiki URL
609		$url = $this-> _resolveInterWiki($wikiName,$wikiUri);
610
611		if ( !$isImage ) {
612			$class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
613			$link['class'] = "interwiki iw_$class";
614		} else {
615			$link['class'] = 'media';
616		}
617
618		//do we stay at the same server? Use local target
619		if( strpos($url,DOKU_URL) === 0 ){
620			$link['target'] = $conf['target']['wiki'];
621		}
622
623		$link['url'] = $url;
624		$link['title'] = htmlspecialchars($link['url']);
625
626		//output formatted
627		$this->doc .= $this->_formatLink($link);
628	}
629
630	/**
631	 */
632	function windowssharelink($url, $name = NULL) {
633		global $conf;
634		global $lang;
635		//simple setup
636		$link['target'] = $conf['target']['windows'];
637		$link['pre']    = '';
638		$link['suf']   = '';
639		$link['style']  = '';
640		//Display error on browsers other than IE
641		$link['more'] = 'onclick="if(document.all == null){alert(\''.
642                        str_replace('\\\\n','\\n',addslashes($lang['nosmblinks'])).
643                        '\');}" onkeypress="if(document.all == null){alert(\''.
644                        str_replace('\\\\n','\\n',addslashes($lang['nosmblinks'])).'\');}"';
645
646		$link['name'] = $this->_getLinkTitle($name, $url, $isImage);
647		if ( !$isImage ) {
648			$link['class'] = 'windows';
649		} else {
650			$link['class'] = 'media';
651		}
652
653
654		$link['title'] = $this->_xmlEntities($url);
655		$url = str_replace('\\','/',$url);
656		$url = 'file:///'.$url;
657		$link['url'] = $url;
658
659		//output formatted
660		$this->doc .= $this->_formatLink($link);
661	}
662
663	function emaillink($address, $name = NULL) {
664		global $conf;
665		//simple setup
666		$link = array();
667		$link['target'] = '';
668		$link['pre']    = '';
669		$link['suf']   = '';
670		$link['style']  = '';
671		$link['more']   = '';
672
673		$name = $this->_getLinkTitle($name, '', $isImage);
674		if ( !$isImage ) {
675			$link['class']='mail JSnocheck';
676		} else {
677			$link['class']='media JSnocheck';
678		}
679
680		$address = $this->_xmlEntities($address);
681		$address = obfuscate($address);
682		$title   = $address;
683
684		if(empty($name)){
685			$name = $address;
686		}
687// #elseif($isImage{
688// 		#            $name = $this->_xmlEntities($name);
689//		#        }
690
691		if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
692
693		$link['url']   = 'mailto:'.$address;
694		$link['name']  = $name;
695		$link['title'] = $title;
696
697		//output formatted
698		$this->doc .= $this->_formatLink($link);
699	}
700
701	function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
702				$height=NULL, $cache=NULL, $linking=NULL) {
703		global $conf;
704		global $ID;
705		resolve_mediaid(getNS($ID),$src, $exists);
706
707		$link = array();
708		$link['class']  = 'media';
709		$link['style']  = '';
710		$link['pre']    = '';
711		$link['suf']    = '';
712		$link['more']   = '';
713		$link['target'] = $conf['target']['media'];
714		$noLink = false;
715
716		$link['title']  = $this->_xmlEntities($src);
717		list($ext,$mime) = mimetype($src);
718		if(substr($mime,0,5) == 'image'){
719			$link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct'));
720		}elseif($mime == 'application/x-shockwave-flash'){
721			// don't link flash movies
722			$noLink = true;
723		}else{
724			// add file icons
725			$class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
726			$link['class'] .= ' mediafile mf_'.$class;
727			$link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true);
728		}
729		$link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
730
731		//output formatted
732		if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
733		else $this->doc .= $this->_formatLink($link);
734	}
735
736/**
737 * @todo don't add link for flash
738 */
739	function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
740				$height=NULL, $cache=NULL, $linking=NULL) {
741		global $conf;
742
743		$link = array();
744		$link['class']  = 'media';
745		$link['style']  = '';
746		$link['pre']    = '';
747		$link['suf']    = '';
748		$link['more']   = '';
749		$link['target'] = $conf['target']['media'];
750
751		$link['title']  = $this->_xmlEntities($src);
752		$link['url']    = ml($src,array('cache'=>$cache));
753		$link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
754		$noLink = false;
755
756		list($ext,$mime) = mimetype($src);
757		if(substr($mime,0,5) == 'image'){
758			// link only jpeg images
759			// if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true;
760		}elseif($mime == 'application/x-shockwave-flash'){
761			// don't link flash movies
762			$noLink = true;
763		}else{
764			// add file icons
765			$link['class'] .= ' mediafile mf_'.$ext;
766		}
767
768		//output formatted
769		if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
770		else $this->doc .= $this->_formatLink($link);
771	}
772
773/**
774 * Renders an RSS feed
775 *
776 * @author Andreas Gohr <andi@splitbrain.org>
777 */
778	function rss ($url,$params){
779		global $lang;
780		global $conf;
781
782		require_once(DOKU_INC.'inc/FeedParser.php');
783		$feed = new FeedParser();
784		$feed->set_feed_url($url);
785
786		//disable warning while fetching
787		if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
788		$rc = $feed->init();
789		if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
790
791		//decide on start and end
792		if($params['reverse']){
793			$mod = -1;
794			$start = $feed->get_item_quantity()-1;
795			$end   = $start - ($params['max']);
796			$end   = ($end < -1) ? -1 : $end;
797		}else{
798			$mod   = 1;
799			$start = 0;
800			$end   = $feed->get_item_quantity();
801			$end   = ($end > $params['max']) ? $params['max'] : $end;;
802		}
803
804		$this->doc .= '<ul class="rss">';
805		if($rc){
806			for ($x = $start; $x != $end; $x += $mod) {
807				$item = $feed->get_item($x);
808				$this->doc .= '<li><div class="li">';
809				$this->externallink($item->get_permalink(),
810						    $item->get_title());
811				if($params['author']){
812					$author = $item->get_author(0);
813					if($author){
814						$name = $author->get_name();
815						if(!$name) $name = $author->get_email();
816						if($name) $this->doc .= ' '.$lang['by'].' '.$name;
817					}
818				}
819				if($params['date']){
820					$this->doc .= ' ('.$item->get_date($conf['dformat']).')';
821				}
822				if($params['details']){
823					$this->doc .= '<div class="detail">';
824					if($conf['htmlok']){
825						$this->doc .= $item->get_description();
826					}else{
827						$this->doc .= strip_tags($item->get_description());
828					}
829					$this->doc .= '</div>';
830				}
831
832				$this->doc .= '</div></li>';
833			}
834		}else{
835			$this->doc .= '<li><div class="li">';
836			$this->doc .= '<em>'.$lang['rssfailed'].'</em>';
837			$this->externallink($url);
838			if($conf['allowdebug']){
839				$this->doc .= '<!--'.hsc($feed->error).'-->';
840			}
841			$this->doc .= '</div></li>';
842		}
843		$this->doc .= '</ul>';
844	}
845
846// $numrows not yet implemented
847	function table_open($maxcols = NULL, $numrows = NULL){
848///        $this->doc .= '<table class="inline">'.DOKU_LF;
849	}
850
851	function table_close(){
852///        $this->doc .= '</table>'.DOKU_LF;
853	}
854
855	function tablerow_open(){
856//        $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB;
857		$this->cell = 0;
858		$this->header_mute = FALSE;
859		$this->doc .= DOKU_LF.'.TP' . DOKU_LF;
860
861	}
862
863	function tablerow_close(){
864//        $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
865	}
866
867	function tableheader_open($colspan = 1, $align = NULL){
868		$this->header_mute = TRUE;
869/* TODO
870 $this->doc .= '<th';
871 if ( !is_null($align) ) {
872 $this->doc .= ' class="'.$align.'align"';
873 }
874 if ( $colspan > 1 ) {
875 $this->doc .= ' colspan="'.$colspan.'"';
876 }
877 $this->doc .= '>';
878*/
879	}
880
881	function tableheader_close(){
882		$this->header_mute = FALSE;
883//        $this->doc .= '</th>';
884	}
885
886	function tablecell_open($colspan = 1, $align = NULL){
887		if ($this->header_mute)
888			return;
889		if ($this->cell == 0)
890			$this->doc .= '\fB';
891/*
892 if ( !is_null($align) ) {
893 $this->doc .= ' class="'.$align.'align"';
894 }
895 if ( $colspan > 1 ) {
896 $this->doc .= ' colspan="'.$colspan.'"';
897 }
898 $this->doc .= '>';
899*/
900	}
901
902	function tablecell_close(){
903		if ($this->header_mute)
904			return;
905		if ($this->cell == 0)
906			$this->doc .= '\fR'.DOKU_LF;
907		else
908			$this->doc .= DOKU_LF;
909		$this->cell++;
910	}
911
912//----------------------------------------------------------
913// Utils
914
915/**
916 * Build a link
917 *
918 * Assembles all parts defined in $link returns HTML for the link
919 *
920 * @author Andreas Gohr <andi@splitbrain.org>
921 */
922	function _formatLink($link){
923		//make sure the url is XHTML compliant (skip mailto)
924		if(substr($link['url'],0,7) != 'mailto:'){
925			$link['url'] = str_replace('&','&amp;',$link['url']);
926			$link['url'] = str_replace('&amp;amp;','&amp;',$link['url']);
927		}
928		//remove double encodings in titles
929		$link['title'] = str_replace('&amp;amp;','&amp;',$link['title']);
930
931		// be sure there are no bad chars in url or title
932		// (we can't do this for name because it can contain an img tag)
933		$link['url']   = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22'));
934		$link['title'] = strtr($link['title'],array('>'=>'&gt;','<'=>'&lt;','"'=>'&quot;'));
935
936		$ret  = '';
937		$ret .= $link['pre'];
938//	$this->urls[] = $link['url'];
939/*
940 $ret .= '<a href="'.$link['url'].'"';
941 if(!empty($link['class']))  $ret .= ' class="'.$link['class'].'"';
942 if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"';
943 if(!empty($link['title']))  $ret .= ' title="'.$link['title'].'"';
944 if(!empty($link['style']))  $ret .= ' style="'.$link['style'].'"';
945 if(!empty($link['more']))   $ret .= ' '.$link['more'];
946 $ret .= '>';
947 $ret .= $link['name'];
948 $ret .= '</a>';
949 $ret .= $link['suf'];
950*/
951		$ret .= $link['name'].'  ['.$link['url'].'] ';
952		return $ret;
953	}
954
955/**
956 * Renders internal and external media
957 *
958 * @author Andreas Gohr <andi@splitbrain.org>
959 */
960	function _media ($src, $title=NULL, $align=NULL, $width=NULL,
961			 $height=NULL, $cache=NULL) {
962
963		$ret = '';
964
965		list($ext,$mime) = mimetype($src);
966		if(substr($mime,0,5) == 'image'){
967			//add image tag
968			$ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
969			$ret .= ' class="media'.$align.'"';
970
971			// make left/right alignment for no-CSS view work (feeds)
972			if($align == 'right') $ret .= ' align="right"';
973			if($align == 'left')  $ret .= ' align="left"';
974
975			if (!is_null($title)) {
976				$ret .= ' title="'.$this->_xmlEntities($title).'"';
977				$ret .= ' alt="'.$this->_xmlEntities($title).'"';
978			}elseif($ext == 'jpg' || $ext == 'jpeg'){
979				//try to use the caption from IPTC/EXIF
980				require_once(DOKU_INC.'inc/JpegMeta.php');
981				$jpeg =& new JpegMeta(mediaFN($src));
982				if($jpeg !== false) $cap = $jpeg->getTitle();
983				if($cap){
984					$ret .= ' title="'.$this->_xmlEntities($cap).'"';
985					$ret .= ' alt="'.$this->_xmlEntities($cap).'"';
986				}else{
987					$ret .= ' alt=""';
988				}
989			}else{
990				$ret .= ' alt=""';
991			}
992
993			if ( !is_null($width) )
994				$ret .= ' width="'.$this->_xmlEntities($width).'"';
995
996			if ( !is_null($height) )
997				$ret .= ' height="'.$this->_xmlEntities($height).'"';
998
999			$ret .= ' />';
1000
1001		}elseif($mime == 'application/x-shockwave-flash'){
1002			$ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'.
1003				' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"';
1004			if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
1005			if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
1006			$ret .= '>'.DOKU_LF;
1007			$ret .= '<param name="movie" value="'.ml($src).'" />'.DOKU_LF;
1008			$ret .= '<param name="quality" value="high" />'.DOKU_LF;
1009			$ret .= '<embed src="'.ml($src).'"'.
1010				' quality="high"';
1011			if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
1012			if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
1013			$ret .= ' type="application/x-shockwave-flash"'.
1014				' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF;
1015			$ret .= '</object>'.DOKU_LF;
1016
1017		}elseif($title){
1018			// well at least we have a title to display
1019			$ret .= $this->_xmlEntities($title);
1020		}else{
1021			// just show the sourcename
1022			$ret .= $this->_xmlEntities(basename(noNS($src)));
1023		}
1024
1025		return $ret;
1026	}
1027
1028	function _xmlEntities($string) {
1029		return htmlspecialchars($string,ENT_QUOTES,'UTF-8');
1030	}
1031
1032/**
1033 * Creates a linkid from a headline
1034 *
1035 * @param string  $title   The headline title
1036 * @param boolean $create  Create a new unique ID?
1037 * @author Andreas Gohr <andi@splitbrain.org>
1038 */
1039	function _headerToLink($title,$create=false) {
1040		$title = str_replace(':','',cleanID($title));
1041		$title = ltrim($title,'0123456789._-');
1042		if(empty($title)) $title='section';
1043
1044		if($create){
1045			// make sure tiles are unique
1046			$num = '';
1047			while(in_array($title.$num,$this->headers)){
1048				($num) ? $num++ : $num = 1;
1049			}
1050			$title = $title.$num;
1051			$this->headers[] = $title;
1052		}
1053
1054		return $title;
1055	}
1056
1057/**
1058 * Construct a title and handle images in titles
1059 *
1060 * @author Harry Fuecks <hfuecks@gmail.com>
1061 */
1062	function _getLinkTitle($title, $default, & $isImage, $id=NULL) {
1063		global $conf;
1064
1065		$isImage = false;
1066		if ( is_null($title) ) {
1067			if ($conf['useheading'] && $id) {
1068				$heading = p_get_first_heading($id,true);
1069				if ($heading) {
1070					return $this->_xmlEntities($heading);
1071				}
1072			}
1073			return $this->_xmlEntities($default);
1074		} else if ( is_string($title) ) {
1075			return $this->_xmlEntities($title);
1076		} else if ( is_array($title) ) {
1077			$isImage = true;
1078			return $this->_imageTitle($title);
1079		}
1080	}
1081
1082/**
1083 * Returns an HTML code for images used in link titles
1084 *
1085 * @todo Resolve namespace on internal images
1086 * @author Andreas Gohr <andi@splitbrain.org>
1087 */
1088	function _imageTitle($img) {
1089		return $this->_media($img['src'],
1090				     $img['title'],
1091				     $img['align'],
1092				     $img['width'],
1093				     $img['height'],
1094				     $img['cache']);
1095	}
1096}
1097
1098//Setup VIM: ex: et ts=4 enc=utf-8 :
1099