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