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