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