Mereged updates from DokuWiki 38
[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     var $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.'" 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).'" 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 .= ' id="'.$hid.'">';
185         $this->doc .= $this->_xmlEntities($text);
186         $this->doc .= "</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.'" 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 class="level'.$level.'">';
340     }
341
342     function listitem_close() {
343         $this->doc .= '</li>'.DOKU_LF;
344     }
345
346     function listcontent_open() {
347         $this->doc .= '<div class="li">';
348     }
349
350     function listcontent_close() {
351         $this->doc .= '</div>'.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   $text      PHP code that is either executed or printed
362      * @param  string   $wrapper   html element to wrap result if $conf['phpok'] is okff
363      *
364      * @author Andreas Gohr <andi@splitbrain.org>
365      */
366     function php($text, $wrapper='code') {
367         global $conf;
368
369         if($conf['phpok']){
370           ob_start();
371           eval($text);
372           $this->doc .= ob_get_contents();
373           ob_end_clean();
374         } else {
375           $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper);
376         }
377     }
378
379     function phpblock($text) {
380         $this->php($text, 'pre');
381     }
382
383     /**
384      * Insert HTML if allowed
385      *
386      * @param  string   $text      html text
387      * @param  string   $wrapper   html element to wrap result if $conf['htmlok'] is okff
388      *
389      * @author Andreas Gohr <andi@splitbrain.org>
390      */
391     function html($text, $wrapper='code') {
392         global $conf;
393
394         if($conf['htmlok']){
395           $this->doc .= $text;
396         } else {
397           $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper);
398         }
399     }
400
401     function htmlblock($text) {
402         $this->html($text, 'pre');
403     }
404
405     function quote_open() {
406         $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
407     }
408
409     function quote_close() {
410         $this->doc .= '</div></blockquote>'.DOKU_LF;
411     }
412
413     function preformatted($text) {
414         $this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text),"\n\r") . '</pre>'. DOKU_LF;
415     }
416
417     function file($text, $language=null, $filename=null) {
418         $this->_highlight('file',$text,$language,$filename);
419     }
420
421     function code($text, $language=null, $filename=null) {
422         $this->_highlight('code',$text,$language,$filename);
423     }
424
425     /**
426      * Use GeSHi to highlight language syntax in code and file blocks
427      *
428      * @author Andreas Gohr <andi@splitbrain.org>
429      */
430     function _highlight($type, $text, $language=null, $filename=null) {
431         global $conf;
432         global $ID;
433         global $lang;
434
435         if($filename){
436             // add icon
437             list($ext) = mimetype($filename,false);
438             $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
439             $class = 'mediafile mf_'.$class;
440
441             $this->doc .= '<dl class="'.$type.'">'.DOKU_LF;
442             $this->doc .= '<dt><a href="'.exportlink($ID,'code',array('codeblock'=>$this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">';
443             $this->doc .= hsc($filename);
444             $this->doc .= '</a></dt>'.DOKU_LF.'<dd>';
445         }
446
447         if ($text{0} == "\n") {
448             $text = substr($text, 1);
449         }
450         if (substr($text, -1) == "\n") {
451             $text = substr($text, 0, -1);
452         }
453
454         if ( is_null($language) ) {
455             $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
456         } else {
457             $class = 'code'; //we always need the code class to make the syntax highlighting apply
458             if($type != 'code') $class .= ' '.$type;
459
460             $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '').'</pre>'.DOKU_LF;
461         }
462
463         if($filename){
464             $this->doc .= '</dd></dl>'.DOKU_LF;
465         }
466
467         $this->_codeblock++;
468     }
469
470     function acronym($acronym) {
471
472         if ( array_key_exists($acronym, $this->acronyms) ) {
473
474             $title = $this->_xmlEntities($this->acronyms[$acronym]);
475
476             $this->doc .= '<abbr title="'.$title
477                 .'">'.$this->_xmlEntities($acronym).'</abbr>';
478
479         } else {
480             $this->doc .= $this->_xmlEntities($acronym);
481         }
482     }
483
484     function smiley($smiley) {
485         if ( array_key_exists($smiley, $this->smileys) ) {
486             $title = $this->_xmlEntities($this->smileys[$smiley]);
487             $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley].
488                 '" class="icon" alt="'.
489                     $this->_xmlEntities($smiley).'" />';
490         } else {
491             $this->doc .= $this->_xmlEntities($smiley);
492         }
493     }
494
495     /*
496     * not used
497     function wordblock($word) {
498         if ( array_key_exists($word, $this->badwords) ) {
499             $this->doc .= '** BLEEP **';
500         } else {
501             $this->doc .= $this->_xmlEntities($word);
502         }
503     }
504     */
505
506     function entity($entity) {
507         if ( array_key_exists($entity, $this->entities) ) {
508             $this->doc .= $this->entities[$entity];
509         } else {
510             $this->doc .= $this->_xmlEntities($entity);
511         }
512     }
513
514     function multiplyentity($x, $y) {
515         $this->doc .= "$x&times;$y";
516     }
517
518     function singlequoteopening() {
519         global $lang;
520         $this->doc .= $lang['singlequoteopening'];
521     }
522
523     function singlequoteclosing() {
524         global $lang;
525         $this->doc .= $lang['singlequoteclosing'];
526     }
527
528     function apostrophe() {
529         global $lang;
530         $this->doc .= $lang['apostrophe'];
531     }
532
533     function doublequoteopening() {
534         global $lang;
535         $this->doc .= $lang['doublequoteopening'];
536     }
537
538     function doublequoteclosing() {
539         global $lang;
540         $this->doc .= $lang['doublequoteclosing'];
541     }
542
543     /**
544     */
545     function camelcaselink($link) {
546       $this->internallink($link,$link);
547     }
548
549
550     function locallink($hash, $name = NULL){
551         global $ID;
552         $name  = $this->_getLinkTitle($name, $hash, $isImage);
553         $hash  = $this->_headerToLink($hash);
554         $title = $ID.' ↵';
555         $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
556         $this->doc .= $name;
557         $this->doc .= '</a>';
558     }
559
560     /**
561      * Render an internal Wiki Link
562      *
563      * $search,$returnonly & $linktype are not for the renderer but are used
564      * elsewhere - no need to implement them in other renderers
565      *
566      * @author Andreas Gohr <andi@splitbrain.org>
567      */
568     function internallink($id, $name = NULL, $search=NULL,$returnonly=false,$linktype='content') {
569         global $conf;
570         global $ID;
571         global $INFO;
572
573         $params = '';
574         $parts = explode('?', $id, 2);
575         if (count($parts) === 2) {
576             $id = $parts[0];
577             $params = $parts[1];
578         }
579
580         // For empty $id we need to know the current $ID
581         // We need this check because _simpleTitle needs
582         // correct $id and resolve_pageid() use cleanID($id)
583         // (some things could be lost)
584         if ($id === '') {
585             $id = $ID;
586         }
587
588         // default name is based on $id as given
589         $default = $this->_simpleTitle($id);
590
591         // now first resolve and clean up the $id
592         resolve_pageid(getNS($ID),$id,$exists);
593
594         $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
595         if ( !$isImage ) {
596             if ( $exists ) {
597                 $class='wikilink1';
598             } else {
599                 $class='wikilink2';
600                 $link['rel']='nofollow';
601             }
602         } else {
603             $class='media';
604         }
605
606         //keep hash anchor
607         list($id,$hash) = explode('#',$id,2);
608         if(!empty($hash)) $hash = $this->_headerToLink($hash);
609
610         //prepare for formating
611         $link['target'] = $conf['target']['wiki'];
612         $link['style']  = '';
613         $link['pre']    = '';
614         $link['suf']    = '';
615         // highlight link to current page
616         if ($id == $INFO['id']) {
617             $link['pre']    = '<span class="curid">';
618             $link['suf']    = '</span>';
619         }
620         $link['more']   = '';
621         $link['class']  = $class;
622         $link['url']    = wl($id, $params);
623         $link['name']   = $name;
624         $link['title']  = $id;
625         //add search string
626         if($search){
627             ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&amp;';
628             if(is_array($search)){
629                 $search = array_map('rawurlencode',$search);
630                 $link['url'] .= 's[]='.join('&amp;s[]=',$search);
631             }else{
632                 $link['url'] .= 's='.rawurlencode($search);
633             }
634         }
635
636         //keep hash
637         if($hash) $link['url'].='#'.$hash;
638
639         //output formatted
640         if($returnonly){
641             return $this->_formatLink($link);
642         }else{
643             $this->doc .= $this->_formatLink($link);
644         }
645     }
646
647     function externallink($url, $name = NULL) {
648         global $conf;
649
650         $name = $this->_getLinkTitle($name, $url, $isImage);
651
652         // url might be an attack vector, only allow registered protocols
653         if(is_null($this->schemes)) $this->schemes = getSchemes();
654         list($scheme) = explode('://',$url);
655         $scheme = strtolower($scheme);
656         if(!in_array($scheme,$this->schemes)) $url = '';
657
658         // is there still an URL?
659         if(!$url){
660             $this->doc .= $name;
661             return;
662         }
663
664         // set class
665         if ( !$isImage ) {
666             $class='urlextern';
667         } else {
668             $class='media';
669         }
670
671         //prepare for formating
672         $link['target'] = $conf['target']['extern'];
673         $link['style']  = '';
674         $link['pre']    = '';
675         $link['suf']    = '';
676         $link['more']   = '';
677         $link['class']  = $class;
678         $link['url']    = $url;
679
680         $link['name']   = $name;
681         $link['title']  = $this->_xmlEntities($url);
682         if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
683
684         //output formatted
685         $this->doc .= $this->_formatLink($link);
686     }
687
688     /**
689     */
690     function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
691         global $conf;
692
693         $link = array();
694         $link['target'] = $conf['target']['interwiki'];
695         $link['pre']    = '';
696         $link['suf']    = '';
697         $link['more']   = '';
698         $link['name']   = $this->_getLinkTitle($name, $wikiUri, $isImage);
699
700         //get interwiki URL
701         $url = $this->_resolveInterWiki($wikiName,$wikiUri);
702
703         if ( !$isImage ) {
704             $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
705             $link['class'] = "interwiki iw_$class";
706         } else {
707             $link['class'] = 'media';
708         }
709
710         //do we stay at the same server? Use local target
711         if( strpos($url,DOKU_URL) === 0 ){
712             $link['target'] = $conf['target']['wiki'];
713         }
714
715         $link['url'] = $url;
716         $link['title'] = htmlspecialchars($link['url']);
717
718         //output formatted
719         $this->doc .= $this->_formatLink($link);
720     }
721
722     /**
723      */
724     function windowssharelink($url, $name = NULL) {
725         global $conf;
726         global $lang;
727         //simple setup
728         $link['target'] = $conf['target']['windows'];
729         $link['pre']    = '';
730         $link['suf']   = '';
731         $link['style']  = '';
732
733         $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
734         if ( !$isImage ) {
735             $link['class'] = 'windows';
736         } else {
737             $link['class'] = 'media';
738         }
739
740
741         $link['title'] = $this->_xmlEntities($url);
742         $url = str_replace('\\','/',$url);
743         $url = 'file:///'.$url;
744         $link['url'] = $url;
745
746         //output formatted
747         $this->doc .= $this->_formatLink($link);
748     }
749
750     function emaillink($address, $name = NULL) {
751         global $conf;
752         //simple setup
753         $link = array();
754         $link['target'] = '';
755         $link['pre']    = '';
756         $link['suf']   = '';
757         $link['style']  = '';
758         $link['more']   = '';
759
760         $name = $this->_getLinkTitle($name, '', $isImage);
761         if ( !$isImage ) {
762             $link['class']='mail';
763         } else {
764             $link['class']='media';
765         }
766
767         $address = $this->_xmlEntities($address);
768         $address = obfuscate($address);
769         $title   = $address;
770
771         if(empty($name)){
772             $name = $address;
773         }
774
775         if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
776
777         $link['url']   = 'mailto:'.$address;
778         $link['name']  = $name;
779         $link['title'] = $title;
780
781         //output formatted
782         $this->doc .= $this->_formatLink($link);
783     }
784
785     function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
786                             $height=NULL, $cache=NULL, $linking=NULL) {
787         global $ID;
788         list($src,$hash) = explode('#',$src,2);
789         resolve_mediaid(getNS($ID),$src, $exists);
790
791         $noLink = false;
792         $render = ($linking == 'linkonly') ? false : true;
793         $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
794
795         list($ext,$mime,$dl) = mimetype($src,false);
796         if(substr($mime,0,5) == 'image' && $render){
797             $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct'));
798         }elseif($mime == 'application/x-shockwave-flash' && $render){
799             // don't link flash movies
800             $noLink = true;
801         }else{
802             // add file icons
803             $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
804             $link['class'] .= ' mediafile mf_'.$class;
805             $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true);
806         }
807
808         if($hash) $link['url'] .= '#'.$hash;
809
810         //markup non existing files
811         if (!$exists) {
812             $link['class'] .= ' wikilink2';
813         }
814
815         //output formatted
816         if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
817         else $this->doc .= $this->_formatLink($link);
818     }
819
820     function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
821                             $height=NULL, $cache=NULL, $linking=NULL) {
822         list($src,$hash) = explode('#',$src,2);
823         $noLink = false;
824         $render = ($linking == 'linkonly') ? false : true;
825         $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
826
827         $link['url']    = ml($src,array('cache'=>$cache));
828
829         list($ext,$mime,$dl) = mimetype($src,false);
830         if(substr($mime,0,5) == 'image' && $render){
831             // link only jpeg images
832             // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true;
833         }elseif($mime == 'application/x-shockwave-flash' && $render){
834             // don't link flash movies
835             $noLink = true;
836         }else{
837             // add file icons
838             $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
839             $link['class'] .= ' mediafile mf_'.$class;
840         }
841
842         if($hash) $link['url'] .= '#'.$hash;
843
844         //output formatted
845         if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
846         else $this->doc .= $this->_formatLink($link);
847     }
848
849     /**
850      * Renders an RSS feed
851      *
852      * @author Andreas Gohr <andi@splitbrain.org>
853      */
854     function rss ($url,$params){
855         global $lang;
856         global $conf;
857
858         require_once(DOKU_INC.'inc/FeedParser.php');
859         $feed = new FeedParser();
860         $feed->set_feed_url($url);
861
862         //disable warning while fetching
863         if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
864         $rc = $feed->init();
865         if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
866
867         //decide on start and end
868         if($params['reverse']){
869             $mod = -1;
870             $start = $feed->get_item_quantity()-1;
871             $end   = $start - ($params['max']);
872             $end   = ($end < -1) ? -1 : $end;
873         }else{
874             $mod   = 1;
875             $start = 0;
876             $end   = $feed->get_item_quantity();
877             $end   = ($end > $params['max']) ? $params['max'] : $end;;
878         }
879
880         $this->doc .= '<ul class="rss">';
881         if($rc){
882             for ($x = $start; $x != $end; $x += $mod) {
883                 $item = $feed->get_item($x);
884                 $this->doc .= '<li><div class="li">';
885                 // support feeds without links
886                 $lnkurl = $item->get_permalink();
887                 if($lnkurl){
888                     // title is escaped by SimplePie, we unescape here because it
889                     // is escaped again in externallink() FS#1705
890                     $this->externallink($item->get_permalink(),
891                                         htmlspecialchars_decode($item->get_title()));
892                 }else{
893                     $this->doc .= ' '.$item->get_title();
894                 }
895                 if($params['author']){
896                     $author = $item->get_author(0);
897                     if($author){
898                         $name = $author->get_name();
899                         if(!$name) $name = $author->get_email();
900                         if($name) $this->doc .= ' '.$lang['by'].' '.$name;
901                     }
902                 }
903                 if($params['date']){
904                     $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
905                 }
906                 if($params['details']){
907                     $this->doc .= '<div class="detail">';
908                     if($conf['htmlok']){
909                         $this->doc .= $item->get_description();
910                     }else{
911                         $this->doc .= strip_tags($item->get_description());
912                     }
913                     $this->doc .= '</div>';
914                 }
915
916                 $this->doc .= '</div></li>';
917             }
918         }else{
919             $this->doc .= '<li><div class="li">';
920             $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
921             $this->externallink($url);
922             if($conf['allowdebug']){
923                 $this->doc .= '<!--'.hsc($feed->error).'-->';
924             }
925             $this->doc .= '</div></li>';
926         }
927         $this->doc .= '</ul>';
928     }
929
930     // $numrows not yet implemented
931     function table_open($maxcols = null, $numrows = null, $pos = null){
932         global $lang;
933         // initialize the row counter used for classes
934         $this->_counter['row_counter'] = 0;
935         $class = 'table';
936         if ($pos !== null) {
937             $class .= ' ' . $this->startSectionEdit($pos, 'table');
938         }
939         $this->doc .= '<div class="' . $class . '"><table class="inline">' .
940                       DOKU_LF;
941     }
942
943     function table_close($pos = null){
944         $this->doc .= '</table></div>'.DOKU_LF;
945         if ($pos !== null) {
946             $this->finishSectionEdit($pos);
947         }
948     }
949
950     function tablerow_open(){
951         // initialize the cell counter used for classes
952         $this->_counter['cell_counter'] = 0;
953         $class = 'row' . $this->_counter['row_counter']++;
954         $this->doc .= DOKU_TAB . '<tr class="'.$class.'">' . DOKU_LF . DOKU_TAB . DOKU_TAB;
955     }
956
957     function tablerow_close(){
958         $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
959     }
960
961     function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1){
962         $class = 'class="col' . $this->_counter['cell_counter']++;
963         if ( !is_null($align) ) {
964             $class .= ' '.$align.'align';
965         }
966         $class .= '"';
967         $this->doc .= '<th ' . $class;
968         if ( $colspan > 1 ) {
969             $this->_counter['cell_counter'] += $colspan-1;
970             $this->doc .= ' colspan="'.$colspan.'"';
971         }
972         if ( $rowspan > 1 ) {
973             $this->doc .= ' rowspan="'.$rowspan.'"';
974         }
975         $this->doc .= '>';
976     }
977
978     function tableheader_close(){
979         $this->doc .= '</th>';
980     }
981
982     function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1){
983         $class = 'class="col' . $this->_counter['cell_counter']++;
984         if ( !is_null($align) ) {
985             $class .= ' '.$align.'align';
986         }
987         $class .= '"';
988         $this->doc .= '<td '.$class;
989         if ( $colspan > 1 ) {
990             $this->_counter['cell_counter'] += $colspan-1;
991             $this->doc .= ' colspan="'.$colspan.'"';
992         }
993         if ( $rowspan > 1 ) {
994             $this->doc .= ' rowspan="'.$rowspan.'"';
995         }
996         $this->doc .= '>';
997     }
998
999     function tablecell_close(){
1000         $this->doc .= '</td>';
1001     }
1002
1003     //----------------------------------------------------------
1004     // Utils
1005
1006     /**
1007      * Build a link
1008      *
1009      * Assembles all parts defined in $link returns HTML for the link
1010      *
1011      * @author Andreas Gohr <andi@splitbrain.org>
1012      */
1013     function _formatLink($link){
1014         //make sure the url is XHTML compliant (skip mailto)
1015         if(substr($link['url'],0,7) != 'mailto:'){
1016             $link['url'] = str_replace('&','&amp;',$link['url']);
1017             $link['url'] = str_replace('&amp;amp;','&amp;',$link['url']);
1018         }
1019         //remove double encodings in titles
1020         $link['title'] = str_replace('&amp;amp;','&amp;',$link['title']);
1021
1022         // be sure there are no bad chars in url or title
1023         // (we can't do this for name because it can contain an img tag)
1024         $link['url']   = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22'));
1025         $link['title'] = strtr($link['title'],array('>'=>'&gt;','<'=>'&lt;','"'=>'&quot;'));
1026
1027         $ret  = '';
1028         $ret .= $link['pre'];
1029         $ret .= '<a href="'.$link['url'].'"';
1030         if(!empty($link['class']))  $ret .= ' class="'.$link['class'].'"';
1031         if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"';
1032         if(!empty($link['title']))  $ret .= ' title="'.$link['title'].'"';
1033         if(!empty($link['style']))  $ret .= ' style="'.$link['style'].'"';
1034         if(!empty($link['rel']))    $ret .= ' rel="'.$link['rel'].'"';
1035         if(!empty($link['more']))   $ret .= ' '.$link['more'];
1036         $ret .= '>';
1037         $ret .= $link['name'];
1038         $ret .= '</a>';
1039         $ret .= $link['suf'];
1040         return $ret;
1041     }
1042
1043     /**
1044      * Renders internal and external media
1045      *
1046      * @author Andreas Gohr <andi@splitbrain.org>
1047      */
1048     function _media ($src, $title=NULL, $align=NULL, $width=NULL,
1049                       $height=NULL, $cache=NULL, $render = true) {
1050
1051         $ret = '';
1052
1053         list($ext,$mime,$dl) = mimetype($src);
1054         if(substr($mime,0,5) == 'image'){
1055             // first get the $title
1056             if (!is_null($title)) {
1057                 $title  = $this->_xmlEntities($title);
1058             }elseif($ext == 'jpg' || $ext == 'jpeg'){
1059                 //try to use the caption from IPTC/EXIF
1060                 require_once(DOKU_INC.'inc/JpegMeta.php');
1061                 $jpeg =new JpegMeta(mediaFN($src));
1062                 if($jpeg !== false) $cap = $jpeg->getTitle();
1063                 if($cap){
1064                     $title = $this->_xmlEntities($cap);
1065                 }
1066             }
1067             if (!$render) {
1068                 // if the picture is not supposed to be rendered
1069                 // return the title of the picture
1070                 if (!$title) {
1071                     // just show the sourcename
1072                     $title = $this->_xmlEntities(utf8_basename(noNS($src)));
1073                 }
1074                 return $title;
1075             }
1076             //add image tag
1077             $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
1078             $ret .= ' class="media'.$align.'"';
1079
1080             if ($title) {
1081                 $ret .= ' title="' . $title . '"';
1082                 $ret .= ' alt="'   . $title .'"';
1083             }else{
1084                 $ret .= ' alt=""';
1085             }
1086
1087             if ( !is_null($width) )
1088                 $ret .= ' width="'.$this->_xmlEntities($width).'"';
1089
1090             if ( !is_null($height) )
1091                 $ret .= ' height="'.$this->_xmlEntities($height).'"';
1092
1093             $ret .= ' />';
1094
1095         }elseif($mime == 'application/x-shockwave-flash'){
1096             if (!$render) {
1097                 // if the flash is not supposed to be rendered
1098                 // return the title of the flash
1099                 if (!$title) {
1100                     // just show the sourcename
1101                     $title = utf8_basename(noNS($src));
1102                 }
1103                 return $this->_xmlEntities($title);
1104             }
1105
1106             $att = array();
1107             $att['class'] = "media$align";
1108             if($align == 'right') $att['align'] = 'right';
1109             if($align == 'left')  $att['align'] = 'left';
1110             $ret .= html_flashobject(ml($src,array('cache'=>$cache),true,'&'),$width,$height,
1111                                      array('quality' => 'high'),
1112                                      null,
1113                                      $att,
1114                                      $this->_xmlEntities($title));
1115         }elseif($title){
1116             // well at least we have a title to display
1117             $ret .= $this->_xmlEntities($title);
1118         }else{
1119             // just show the sourcename
1120             $ret .= $this->_xmlEntities(utf8_basename(noNS($src)));
1121         }
1122
1123         return $ret;
1124     }
1125
1126     function _xmlEntities($string) {
1127         return htmlspecialchars($string,ENT_QUOTES,'UTF-8');
1128     }
1129
1130     /**
1131      * Creates a linkid from a headline
1132      *
1133      * @param string  $title   The headline title
1134      * @param boolean $create  Create a new unique ID?
1135      * @author Andreas Gohr <andi@splitbrain.org>
1136      */
1137     function _headerToLink($title,$create=false) {
1138         if($create){
1139             return sectionID($title,$this->headers);
1140         }else{
1141             $check = false;
1142             return sectionID($title,$check);
1143         }
1144     }
1145
1146     /**
1147      * Construct a title and handle images in titles
1148      *
1149      * @author Harry Fuecks <hfuecks@gmail.com>
1150      */
1151     function _getLinkTitle($title, $default, & $isImage, $id=NULL, $linktype='content') {
1152         global $conf;
1153
1154         $isImage = false;
1155         if ( is_array($title) ) {
1156             $isImage = true;
1157             return $this->_imageTitle($title);
1158         } elseif ( is_null($title) || trim($title)=='') {
1159             if (useHeading($linktype) && $id) {
1160                 $heading = p_get_first_heading($id);
1161                 if ($heading) {
1162                     return $this->_xmlEntities($heading);
1163                 }
1164             }
1165             return $this->_xmlEntities($default);
1166         } else {
1167             return $this->_xmlEntities($title);
1168         }
1169     }
1170
1171     /**
1172      * Returns an HTML code for images used in link titles
1173      *
1174      * @todo Resolve namespace on internal images
1175      * @author Andreas Gohr <andi@splitbrain.org>
1176      */
1177     function _imageTitle($img) {
1178         global $ID;
1179
1180         // some fixes on $img['src']
1181         // see internalmedia() and externalmedia()
1182         list($img['src'],$hash) = explode('#',$img['src'],2);
1183         if ($img['type'] == 'internalmedia') {
1184             resolve_mediaid(getNS($ID),$img['src'],$exists);
1185         }
1186
1187         return $this->_media($img['src'],
1188                               $img['title'],
1189                               $img['align'],
1190                               $img['width'],
1191                               $img['height'],
1192                               $img['cache']);
1193     }
1194
1195     /**
1196      * _getMediaLinkConf is a helperfunction to internalmedia() and externalmedia()
1197      * which returns a basic link to a media.
1198      *
1199      * @author Pierre Spring <pierre.spring@liip.ch>
1200      * @param string $src
1201      * @param string $title
1202      * @param string $align
1203      * @param string $width
1204      * @param string $height
1205      * @param string $cache
1206      * @param string $render
1207      * @access protected
1208      * @return array
1209      */
1210     function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render)
1211     {
1212         global $conf;
1213
1214         $link = array();
1215         $link['class']  = 'media';
1216         $link['style']  = '';
1217         $link['pre']    = '';
1218         $link['suf']    = '';
1219         $link['more']   = '';
1220         $link['target'] = $conf['target']['media'];
1221         $link['title']  = $this->_xmlEntities($src);
1222         $link['name']   = $this->_media($src, $title, $align, $width, $height, $cache, $render);
1223
1224         return $link;
1225     }
1226
1227
1228 }
1229
1230 //Setup VIM: ex: et ts=4 :