xref: /dokuwiki/inc/parser/xhtml.php (revision b8bf26bf50e6819d43f6662e061224f1c48445e6)
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        $link['name']   = $name;
566        $link['title']  = $this->_xmlEntities($url);
567        if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
568
569        //output formatted
570        $this->doc .= $this->_formatLink($link);
571    }
572
573    /**
574    */
575    function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
576        global $conf;
577
578        $link = array();
579        $link['target'] = $conf['target']['interwiki'];
580        $link['pre']    = '';
581        $link['suf']    = '';
582        $link['more']   = '';
583        $link['name']   = $this->_getLinkTitle($name, $wikiUri, $isImage);
584
585        //get interwiki URL
586        if ( isset($this->interwiki[$wikiName]) ) {
587            $url = $this->interwiki[$wikiName];
588        } else {
589            // Default to Google I'm feeling lucky
590            $url = 'http://www.google.com/search?q={URL}&amp;btnI=lucky';
591            $wikiName = 'go';
592        }
593
594        if ( !$isImage ) {
595            $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
596            $link['class'] = "interwiki iw_$class";
597        } else {
598            $link['class'] = 'media';
599        }
600
601        //do we stay at the same server? Use local target
602        if( strpos($url,DOKU_URL) === 0 ){
603            $link['target'] = $conf['target']['wiki'];
604        }
605
606        //split into hash and url part
607        list($wikiUri,$hash) = explode('#',$wikiUri,2);
608
609        //replace placeholder
610        if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#',$url)){
611            //use placeholders
612            $url = str_replace('{URL}',rawurlencode($wikiUri),$url);
613            $url = str_replace('{NAME}',$wikiUri,$url);
614            $parsed = parse_url($wikiUri);
615            if(!$parsed['port']) $parsed['port'] = 80;
616            $url = str_replace('{SCHEME}',$parsed['scheme'],$url);
617            $url = str_replace('{HOST}',$parsed['host'],$url);
618            $url = str_replace('{PORT}',$parsed['port'],$url);
619            $url = str_replace('{PATH}',$parsed['path'],$url);
620            $url = str_replace('{QUERY}',$parsed['query'],$url);
621            $link['url'] = $url;
622        }else{
623            //default
624            $link['url'] = $url.rawurlencode($wikiUri);
625        }
626        if($hash) $link['url'] .= '#'.rawurlencode($hash);
627
628        $link['title'] = htmlspecialchars($link['url']);
629
630        //output formatted
631        $this->doc .= $this->_formatLink($link);
632    }
633
634    /**
635     */
636    function windowssharelink($url, $name = NULL) {
637        global $conf;
638        global $lang;
639        //simple setup
640        $link['target'] = $conf['target']['windows'];
641        $link['pre']    = '';
642        $link['suf']   = '';
643        $link['style']  = '';
644        //Display error on browsers other than IE
645        $link['more'] = 'onclick="if(document.all == null){alert(\''.
646                        $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).
647                        '\');}" onkeypress="if(document.all == null){alert(\''.
648                        $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"';
649
650        $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
651        if ( !$isImage ) {
652            $link['class'] = 'windows';
653        } else {
654            $link['class'] = 'media';
655        }
656
657
658        $link['title'] = $this->_xmlEntities($url);
659        $url = str_replace('\\','/',$url);
660        $url = 'file:///'.$url;
661        $link['url'] = $url;
662
663        //output formatted
664        $this->doc .= $this->_formatLink($link);
665    }
666
667    function emaillink($address, $name = NULL) {
668        global $conf;
669        //simple setup
670        $link = array();
671        $link['target'] = '';
672        $link['pre']    = '';
673        $link['suf']   = '';
674        $link['style']  = '';
675        $link['more']   = '';
676
677        //we just test for image here - we need to encode the title our self
678        $this->_getLinkTitle($name, $address, $isImage);
679        if ( !$isImage ) {
680            $link['class']='mail JSnocheck';
681        } else {
682            $link['class']='media JSnocheck';
683        }
684
685        $address = $this->_xmlEntities($address);
686        $address = obfuscate($address);
687        $title   = $address;
688        if(empty($name)){
689            $name = $address;
690        }else{
691            $name = $this->_xmlEntities($name);
692        }
693
694        if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
695
696        $link['url']   = 'mailto:'.$address;
697        $link['name']  = $name;
698        $link['title'] = $title;
699
700        //output formatted
701        $this->doc .= $this->_formatLink($link);
702    }
703
704    function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
705                            $height=NULL, $cache=NULL, $linking=NULL) {
706        global $conf;
707        global $ID;
708        resolve_mediaid(getNS($ID),$src, $exists);
709
710        $link = array();
711        $link['class']  = 'media';
712        $link['style']  = '';
713        $link['pre']    = '';
714        $link['suf']    = '';
715        $link['more']   = '';
716        $link['target'] = $conf['target']['media'];
717
718        $link['title']  = $this->_xmlEntities($src);
719        list($ext,$mime) = mimetype($src);
720        if(substr($mime,0,5) == 'image'){
721             $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct'));
722         }elseif($mime == 'application/x-shockwave-flash'){
723             // don't link flash movies
724             $noLink = TRUE;
725         }else{
726             // add file icons
727             $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
728             $link['class'] .= ' mediafile mf_'.$class;
729             $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true);
730         }
731         $link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
732
733         //output formatted
734         if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
735         else $this->doc .= $this->_formatLink($link);
736    }
737
738    /**
739     * @todo don't add link for flash
740     */
741    function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
742                            $height=NULL, $cache=NULL, $linking=NULL) {
743        global $conf;
744
745        $link = array();
746        $link['class']  = 'media';
747        $link['style']  = '';
748        $link['pre']    = '';
749        $link['suf']    = '';
750        $link['more']   = '';
751        $link['target'] = $conf['target']['media'];
752
753        $link['title']  = $this->_xmlEntities($src);
754        $link['url']    = ml($src,array('cache'=>$cache));
755        $link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
756
757
758        list($ext,$mime) = mimetype($src);
759        if(substr($mime,0,5) == 'image'){
760             // link only jpeg images
761             // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = TRUE;
762        }elseif($mime == 'application/x-shockwave-flash'){
763             // don't link flash movies
764             $noLink = TRUE;
765        }else{
766             // add file icons
767             $link['class'] .= ' mediafile mf_'.$ext;
768         }
769
770        //output formatted
771        if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
772        else $this->doc .= $this->_formatLink($link);
773    }
774
775    /**
776     * Renders an RSS feed using Magpie
777     *
778     * @author Andreas Gohr <andi@splitbrain.org>
779     */
780    function rss ($url){
781        global $lang;
782        define('MAGPIE_CACHE_ON', false); //we do our own caching
783        define('MAGPIE_DIR', DOKU_INC.'inc/magpie/');
784        define('MAGPIE_OUTPUT_ENCODING','UTF-8'); //return all feeds as UTF-8
785        require_once(MAGPIE_DIR.'/rss_fetch.inc');
786
787        //disable warning while fetching
788        $elvl = error_reporting(E_ERROR);
789        $rss  = fetch_rss($url);
790        error_reporting($elvl);
791
792        $this->doc .= '<ul class="rss">';
793        if($rss){
794            foreach ($rss->items as $item ) {
795                $this->doc .= '<li>';
796                $this->externallink($item['link'],$item['title']);
797                $this->doc .= '</li>';
798            }
799        }else{
800            $this->doc .= '<li>';
801            $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
802            $this->externallink($url);
803            $this->doc .= '</li>';
804        }
805        $this->doc .= '</ul>';
806    }
807
808    // $numrows not yet implemented
809    function table_open($maxcols = NULL, $numrows = NULL){
810        $this->doc .= '<table class="inline">'.DOKU_LF;
811    }
812
813    function table_close(){
814        $this->doc .= '</table>'.DOKU_LF.'<br />'.DOKU_LF;
815    }
816
817    function tablerow_open(){
818        $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB;
819    }
820
821    function tablerow_close(){
822        $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
823    }
824
825    function tableheader_open($colspan = 1, $align = NULL){
826        $this->doc .= '<th';
827        if ( !is_null($align) ) {
828            $this->doc .= ' class="'.$align.'align"';
829        }
830        if ( $colspan > 1 ) {
831            $this->doc .= ' colspan="'.$colspan.'"';
832        }
833        $this->doc .= '>';
834    }
835
836    function tableheader_close(){
837        $this->doc .= '</th>';
838    }
839
840    function tablecell_open($colspan = 1, $align = NULL){
841        $this->doc .= '<td';
842        if ( !is_null($align) ) {
843            $this->doc .= ' class="'.$align.'align"';
844        }
845        if ( $colspan > 1 ) {
846            $this->doc .= ' colspan="'.$colspan.'"';
847        }
848        $this->doc .= '>';
849    }
850
851    function tablecell_close(){
852        $this->doc .= '</td>';
853    }
854
855    //----------------------------------------------------------
856    // Utils
857
858    /**
859     * Build a link
860     *
861     * Assembles all parts defined in $link returns HTML for the link
862     *
863     * @author Andreas Gohr <andi@splitbrain.org>
864     */
865    function _formatLink($link){
866        //make sure the url is XHTML compliant (skip mailto)
867        if(substr($link['url'],0,7) != 'mailto:'){
868            $link['url'] = str_replace('&','&amp;',$link['url']);
869            $link['url'] = str_replace('&amp;amp;','&amp;',$link['url']);
870        }
871        //remove double encodings in titles
872        $link['title'] = str_replace('&amp;amp;','&amp;',$link['title']);
873
874        // be sure there are no bad chars in url or title
875        // (we can't do this for name because it can contain an img tag)
876        $link['url']   = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22'));
877        $link['title'] = strtr($link['title'],array('>'=>'&gt;','<'=>'&lt;','"'=>'&quot;'));
878
879        $ret  = '';
880        $ret .= $link['pre'];
881        $ret .= '<a href="'.$link['url'].'"';
882        if($link['class'])  $ret .= ' class="'.$link['class'].'"';
883        if($link['target']) $ret .= ' target="'.$link['target'].'"';
884        if($link['title'])  $ret .= ' title="'.$link['title'].'"';
885        if($link['style'])  $ret .= ' style="'.$link['style'].'"';
886        if($link['more'])   $ret .= ' '.$link['more'];
887        $ret .= '>';
888        $ret .= $link['name'];
889        $ret .= '</a>';
890        $ret .= $link['suf'];
891        return $ret;
892    }
893
894    /**
895     * Removes any Namespace from the given name but keeps
896     * casing and special chars
897     *
898     * @author Andreas Gohr <andi@splitbrain.org>
899     */
900    function _simpleTitle($name){
901        global $conf;
902
903        if($conf['useslash']){
904            $nssep = '[:;/]';
905        }else{
906            $nssep = '[:;]';
907        }
908        $name = preg_replace('!.*'.$nssep.'!','',$name);
909        //if there is a hash we use the ancor name only
910        $name = preg_replace('!.*#!','',$name);
911        return $name;
912    }
913
914    /**
915     * Renders internal and external media
916     *
917     * @author Andreas Gohr <andi@splitbrain.org>
918     */
919    function _media ($src, $title=NULL, $align=NULL, $width=NULL,
920                      $height=NULL, $cache=NULL) {
921
922        $ret = '';
923
924        list($ext,$mime) = mimetype($src);
925        if(substr($mime,0,5) == 'image'){
926            //add image tag
927            $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
928            $ret .= ' class="media'.$align.'"';
929
930            if (!is_null($title)) {
931                $ret .= ' title="'.$this->_xmlEntities($title).'"';
932                $ret .= ' alt="'.$this->_xmlEntities($title).'"';
933            }elseif($ext == 'jpg' || $ext == 'jpeg'){
934                //try to use the caption from IPTC/EXIF
935                require_once(DOKU_INC.'inc/JpegMeta.php');
936                $jpeg =& new JpegMeta(mediaFN($src));
937                if($jpeg !== false) $cap = $jpeg->getTitle();
938                if($cap){
939                    $ret .= ' title="'.$this->_xmlEntities($cap).'"';
940                    $ret .= ' alt="'.$this->_xmlEntities($cap).'"';
941                }
942            }else{
943                $ret .= ' alt=""';
944            }
945
946            if ( !is_null($width) )
947                $ret .= ' width="'.$this->_xmlEntities($width).'"';
948
949            if ( !is_null($height) )
950                $ret .= ' height="'.$this->_xmlEntities($height).'"';
951
952            $ret .= ' />';
953
954        }elseif($mime == 'application/x-shockwave-flash'){
955            $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'.
956                    ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"';
957            if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
958            if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
959            $ret .= '>'.DOKU_LF;
960            $ret .= '<param name="movie" value="'.ml($src).'" />'.DOKU_LF;
961            $ret .= '<param name="quality" value="high" />'.DOKU_LF;
962            $ret .= '<embed src="'.ml($src).'"'.
963                    ' quality="high"';
964            if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
965            if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
966            $ret .= ' type="application/x-shockwave-flash"'.
967                    ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF;
968            $ret .= '</object>'.DOKU_LF;
969
970        }elseif(!is_null($title)){
971            // well at least we have a title to display
972            $ret .= $this->_xmlEntities($title);
973        }else{
974            // just show the sourcename
975            $ret .= $this->_xmlEntities(noNS($src));
976        }
977
978        return $ret;
979    }
980
981    function _xmlEntities($string) {
982        return htmlspecialchars($string);
983    }
984
985    /**
986     * Creates a linkid from a headline
987     *
988     * @param string  $title   The headline title
989     * @param boolean $create  Create a new unique ID?
990     * @author Andreas Gohr <andi@splitbrain.org>
991     */
992    function _headerToLink($title,$create=false) {
993        $title = str_replace(':','',cleanID($title,true)); //force ASCII
994        $title = ltrim($title,'0123456789._-');
995        if(empty($title)) $title='section';
996
997        if($create){
998            // make sure tiles are unique
999            $num = '';
1000            while(in_array($title.$num,$this->headers)){
1001                ($num) ? $num++ : $num = 1;
1002            }
1003            $title = $title.$num;
1004            $this->headers[] = $title;
1005        }
1006
1007        return $title;
1008    }
1009
1010    /**
1011     * Adds code for section editing button
1012     *
1013     * This is just aplaceholder and gets replace by the button if
1014     * section editing is allowed
1015     *
1016     * @author Andreas Gohr <andi@splitbrain.org>
1017     */
1018    function _secedit($f, $t){
1019        $this->doc .= '<!-- SECTION ['.$f.'-'.$t.'] -->';
1020    }
1021
1022    /**
1023     * Construct a title and handle images in titles
1024     *
1025     * @author Harry Fuecks <hfuecks@gmail.com>
1026     */
1027    function _getLinkTitle($title, $default, & $isImage, $id=NULL) {
1028        global $conf;
1029
1030        $isImage = FALSE;
1031        if ( is_null($title) ) {
1032            if ($conf['useheading'] && $id) {
1033                $heading = p_get_first_heading($id);
1034                if ($heading) {
1035                    return $this->_xmlEntities($heading);
1036                }
1037            }
1038            return $this->_xmlEntities($default);
1039        } else if ( is_string($title) ) {
1040            return $this->_xmlEntities($title);
1041        } else if ( is_array($title) ) {
1042            $isImage = TRUE;
1043            return $this->_imageTitle($title);
1044        }
1045    }
1046
1047    /**
1048     * Returns an HTML code for images used in link titles
1049     *
1050     * @todo Resolve namespace on internal images
1051     * @author Andreas Gohr <andi@splitbrain.org>
1052     */
1053    function _imageTitle($img) {
1054        return $this->_media($img['src'],
1055                              $img['title'],
1056                              $img['align'],
1057                              $img['width'],
1058                              $img['height'],
1059                              $img['cache']);
1060    }
1061}
1062
1063//Setup VIM: ex: et ts=4 enc=utf-8 :
1064