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