3 * Renderer for XHTML output
5 * @author Harry Fuecks <hfuecks@gmail.com>
6 * @author Andreas Gohr <andi@splitbrain.org>
8 if(!defined('DOKU_INC')) die('meh.');
10 if ( !defined('DOKU_LF') ) {
11 // Some whitespace to help View > Source
12 define ('DOKU_LF',"\n");
15 if ( !defined('DOKU_TAB') ) {
16 // Some whitespace to help View > Source
17 define ('DOKU_TAB',"\t");
20 require_once DOKU_INC . 'inc/parser/renderer.php';
21 require_once DOKU_INC . 'inc/html.php';
26 class Doku_Renderer_xhtml extends Doku_Renderer {
29 var $doc = ''; // will contain the whole document
30 var $toc = array(); // will contain the Table of Contents
32 var $sectionedits = array(); // A stack of section edit data
34 var $headers = array();
35 var $footnotes = array();
37 var $node = array(0,0,0,0,0);
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
44 * Register a new edit section range
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>
52 public function startSectionEdit($start, $type, $title = null) {
53 static $lastsecid = 0;
54 $this->sectionedits[] = array(++$lastsecid, $start, $type, $title);
55 return 'sectionedit' . $lastsecid;
59 * Finish an edit section range
61 * @param $end int The byte position for the edit end; null for the rest of
63 * @author Adrian Lang <lang@cosmocode.de>
65 public function finishSectionEdit($end = null) {
66 list($id, $start, $type, $title) = array_pop($this->sectionedits);
67 if (!is_null($end) && $end <= $start) {
70 $this->doc .= "<!-- EDIT$id " . strtoupper($type) . ' ';
71 if (!is_null($title)) {
72 $this->doc .= '"' . str_replace('"', '', $title) . '" ';
74 $this->doc .= "[$start-" . (is_null($end) ? '' : $end) . '] -->';
82 function document_start() {
83 //reset some internals
85 $this->headers = array();
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
94 array_pop($this->sectionedits);
96 $this->finishSectionEdit();
100 if ( count ($this->footnotes) > 0 ) {
101 $this->doc .= '<div class="footnotes">'.DOKU_LF;
104 foreach ( $this->footnotes as $footnote ) {
105 $id++; // the number of the current footnote
107 // check its not a placeholder that indicates actual footnote text is elsewhere
108 if (substr($footnote, 0, 5) != "@@FNT") {
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;
115 // get any other footnotes that use the same markup
116 $alt = array_keys($this->footnotes, "@@FNT$id");
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;
126 // add footnote markup and close this footnote
127 $this->doc .= $footnote;
128 $this->doc .= '</div>' . DOKU_LF;
131 $this->doc .= '</div>'.DOKU_LF;
136 if($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']){
141 // make sure there are no empty paragraphs
142 $this->doc = preg_replace('#<p>\s*</p>#','',$this->doc);
145 function toc_additem($id, $text, $level) {
149 if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){
150 $this->toc[] = html_mktocitem($id, $text, $level-$conf['toptoclevel']+1);
154 function header($text, $level, $pos) {
157 if(!$text) return; //skip empty headlines
159 $hid = $this->_headerToLink($text,true);
161 //only add items within configured levels
162 $this->toc_additem($hid, $text, $level);
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;
171 $this->lastlevel = $level;
173 if ($level <= $conf['maxseclevel'] &&
174 count($this->sectionedits) > 0 &&
175 $this->sectionedits[count($this->sectionedits) - 1][2] === 'section') {
176 $this->finishSectionEdit($pos - 1);
180 $this->doc .= DOKU_LF.'<h'.$level;
181 if ($level <= $conf['maxseclevel']) {
182 $this->doc .= ' class="' . $this->startSectionEdit($pos, 'section', $text) . '"';
184 $this->doc .= ' id="'.$hid.'">';
185 $this->doc .= $this->_xmlEntities($text);
186 $this->doc .= "</h$level>".DOKU_LF;
189 function section_open($level) {
190 $this->doc .= '<div class="level' . $level . '">' . DOKU_LF;
193 function section_close() {
194 $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
197 function cdata($text) {
198 $this->doc .= $this->_xmlEntities($text);
202 $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
206 $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
209 function linebreak() {
210 $this->doc .= '<br/>'.DOKU_LF;
214 $this->doc .= '<hr />'.DOKU_LF;
217 function strong_open() {
218 $this->doc .= '<strong>';
221 function strong_close() {
222 $this->doc .= '</strong>';
225 function emphasis_open() {
226 $this->doc .= '<em>';
229 function emphasis_close() {
230 $this->doc .= '</em>';
233 function underline_open() {
234 $this->doc .= '<em class="u">';
237 function underline_close() {
238 $this->doc .= '</em>';
241 function monospace_open() {
242 $this->doc .= '<code>';
245 function monospace_close() {
246 $this->doc .= '</code>';
249 function subscript_open() {
250 $this->doc .= '<sub>';
253 function subscript_close() {
254 $this->doc .= '</sub>';
257 function superscript_open() {
258 $this->doc .= '<sup>';
261 function superscript_close() {
262 $this->doc .= '</sup>';
265 function deleted_open() {
266 $this->doc .= '<del>';
269 function deleted_close() {
270 $this->doc .= '</del>';
274 * Callback for footnote start syntax
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
280 * @author Andreas Gohr <andi@splitbrain.org>
282 function footnote_open() {
284 // move current content to store and record footnote
285 $this->store = $this->doc;
290 * Callback for footnote end syntax
292 * All rendered content is moved to the $footnotes array and the old
293 * content is restored from $store again
295 * @author Andreas Gohr
297 function footnote_close() {
299 // recover footnote into the stack and restore old content
300 $footnote = $this->doc;
301 $this->doc = $this->store;
304 // check to see if this footnote has been seen before
305 $i = array_search($footnote, $this->footnotes);
308 // its a new footnote, add it to the $footnotes array
309 $id = count($this->footnotes)+1;
310 $this->footnotes[count($this->footnotes)] = $footnote;
312 // seen this one before, translate the index to an id and save a placeholder
314 $id = count($this->footnotes)+1;
315 $this->footnotes[count($this->footnotes)] = "@@FNT".($i);
318 // output the footnote reference and link
319 $this->doc .= '<sup><a href="#fn__'.$id.'" id="fnt__'.$id.'" class="fn_top">'.$id.')</a></sup>';
322 function listu_open() {
323 $this->doc .= '<ul>'.DOKU_LF;
326 function listu_close() {
327 $this->doc .= '</ul>'.DOKU_LF;
330 function listo_open() {
331 $this->doc .= '<ol>'.DOKU_LF;
334 function listo_close() {
335 $this->doc .= '</ol>'.DOKU_LF;
338 function listitem_open($level) {
339 $this->doc .= '<li class="level'.$level.'">';
342 function listitem_close() {
343 $this->doc .= '</li>'.DOKU_LF;
346 function listcontent_open() {
347 $this->doc .= '<div class="li">';
350 function listcontent_close() {
351 $this->doc .= '</div>'.DOKU_LF;
354 function unformatted($text) {
355 $this->doc .= $this->_xmlEntities($text);
359 * Execute PHP code if allowed
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
364 * @author Andreas Gohr <andi@splitbrain.org>
366 function php($text, $wrapper='code') {
372 $this->doc .= ob_get_contents();
375 $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper);
379 function phpblock($text) {
380 $this->php($text, 'pre');
384 * Insert HTML if allowed
386 * @param string $text html text
387 * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff
389 * @author Andreas Gohr <andi@splitbrain.org>
391 function html($text, $wrapper='code') {
397 $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper);
401 function htmlblock($text) {
402 $this->html($text, 'pre');
405 function quote_open() {
406 $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
409 function quote_close() {
410 $this->doc .= '</div></blockquote>'.DOKU_LF;
413 function preformatted($text) {
414 $this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text),"\n\r") . '</pre>'. DOKU_LF;
417 function file($text, $language=null, $filename=null) {
418 $this->_highlight('file',$text,$language,$filename);
421 function code($text, $language=null, $filename=null) {
422 $this->_highlight('code',$text,$language,$filename);
426 * Use GeSHi to highlight language syntax in code and file blocks
428 * @author Andreas Gohr <andi@splitbrain.org>
430 function _highlight($type, $text, $language=null, $filename=null) {
437 list($ext) = mimetype($filename,false);
438 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
439 $class = 'mediafile mf_'.$class;
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>';
447 if ($text{0} == "\n") {
448 $text = substr($text, 1);
450 if (substr($text, -1) == "\n") {
451 $text = substr($text, 0, -1);
454 if ( is_null($language) ) {
455 $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
457 $class = 'code'; //we always need the code class to make the syntax highlighting apply
458 if($type != 'code') $class .= ' '.$type;
460 $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '').'</pre>'.DOKU_LF;
464 $this->doc .= '</dd></dl>'.DOKU_LF;
470 function acronym($acronym) {
472 if ( array_key_exists($acronym, $this->acronyms) ) {
474 $title = $this->_xmlEntities($this->acronyms[$acronym]);
476 $this->doc .= '<abbr title="'.$title
477 .'">'.$this->_xmlEntities($acronym).'</abbr>';
480 $this->doc .= $this->_xmlEntities($acronym);
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).'" />';
491 $this->doc .= $this->_xmlEntities($smiley);
497 function wordblock($word) {
498 if ( array_key_exists($word, $this->badwords) ) {
499 $this->doc .= '** BLEEP **';
501 $this->doc .= $this->_xmlEntities($word);
506 function entity($entity) {
507 if ( array_key_exists($entity, $this->entities) ) {
508 $this->doc .= $this->entities[$entity];
510 $this->doc .= $this->_xmlEntities($entity);
514 function multiplyentity($x, $y) {
515 $this->doc .= "$x×$y";
518 function singlequoteopening() {
520 $this->doc .= $lang['singlequoteopening'];
523 function singlequoteclosing() {
525 $this->doc .= $lang['singlequoteclosing'];
528 function apostrophe() {
530 $this->doc .= $lang['apostrophe'];
533 function doublequoteopening() {
535 $this->doc .= $lang['doublequoteopening'];
538 function doublequoteclosing() {
540 $this->doc .= $lang['doublequoteclosing'];
545 function camelcaselink($link) {
546 $this->internallink($link,$link);
550 function locallink($hash, $name = NULL){
552 $name = $this->_getLinkTitle($name, $hash, $isImage);
553 $hash = $this->_headerToLink($hash);
555 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
557 $this->doc .= '</a>';
561 * Render an internal Wiki Link
563 * $search,$returnonly & $linktype are not for the renderer but are used
564 * elsewhere - no need to implement them in other renderers
566 * @author Andreas Gohr <andi@splitbrain.org>
568 function internallink($id, $name = NULL, $search=NULL,$returnonly=false,$linktype='content') {
574 $parts = explode('?', $id, 2);
575 if (count($parts) === 2) {
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)
588 // default name is based on $id as given
589 $default = $this->_simpleTitle($id);
591 // now first resolve and clean up the $id
592 resolve_pageid(getNS($ID),$id,$exists);
594 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
600 $link['rel']='nofollow';
607 list($id,$hash) = explode('#',$id,2);
608 if(!empty($hash)) $hash = $this->_headerToLink($hash);
610 //prepare for formating
611 $link['target'] = $conf['target']['wiki'];
615 // highlight link to current page
616 if ($id == $INFO['id']) {
617 $link['pre'] = '<span class="curid">';
618 $link['suf'] = '</span>';
621 $link['class'] = $class;
622 $link['url'] = wl($id, $params);
623 $link['name'] = $name;
624 $link['title'] = $id;
627 ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&';
628 if(is_array($search)){
629 $search = array_map('rawurlencode',$search);
630 $link['url'] .= 's[]='.join('&s[]=',$search);
632 $link['url'] .= 's='.rawurlencode($search);
637 if($hash) $link['url'].='#'.$hash;
641 return $this->_formatLink($link);
643 $this->doc .= $this->_formatLink($link);
647 function externallink($url, $name = NULL) {
650 $name = $this->_getLinkTitle($name, $url, $isImage);
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 = '';
658 // is there still an URL?
671 //prepare for formating
672 $link['target'] = $conf['target']['extern'];
677 $link['class'] = $class;
680 $link['name'] = $name;
681 $link['title'] = $this->_xmlEntities($url);
682 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
685 $this->doc .= $this->_formatLink($link);
690 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
694 $link['target'] = $conf['target']['interwiki'];
698 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage);
701 $url = $this->_resolveInterWiki($wikiName,$wikiUri);
704 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
705 $link['class'] = "interwiki iw_$class";
707 $link['class'] = 'media';
710 //do we stay at the same server? Use local target
711 if( strpos($url,DOKU_URL) === 0 ){
712 $link['target'] = $conf['target']['wiki'];
716 $link['title'] = htmlspecialchars($link['url']);
719 $this->doc .= $this->_formatLink($link);
724 function windowssharelink($url, $name = NULL) {
728 $link['target'] = $conf['target']['windows'];
733 $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
735 $link['class'] = 'windows';
737 $link['class'] = 'media';
741 $link['title'] = $this->_xmlEntities($url);
742 $url = str_replace('\\','/',$url);
743 $url = 'file:///'.$url;
747 $this->doc .= $this->_formatLink($link);
750 function emaillink($address, $name = NULL) {
754 $link['target'] = '';
760 $name = $this->_getLinkTitle($name, '', $isImage);
762 $link['class']='mail';
764 $link['class']='media';
767 $address = $this->_xmlEntities($address);
768 $address = obfuscate($address);
775 if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
777 $link['url'] = 'mailto:'.$address;
778 $link['name'] = $name;
779 $link['title'] = $title;
782 $this->doc .= $this->_formatLink($link);
785 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
786 $height=NULL, $cache=NULL, $linking=NULL) {
788 list($src,$hash) = explode('#',$src,2);
789 resolve_mediaid(getNS($ID),$src, $exists);
792 $render = ($linking == 'linkonly') ? false : true;
793 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
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
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);
808 if($hash) $link['url'] .= '#'.$hash;
810 //markup non existing files
812 $link['class'] .= ' wikilink2';
816 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
817 else $this->doc .= $this->_formatLink($link);
820 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
821 $height=NULL, $cache=NULL, $linking=NULL) {
822 list($src,$hash) = explode('#',$src,2);
824 $render = ($linking == 'linkonly') ? false : true;
825 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
827 $link['url'] = ml($src,array('cache'=>$cache));
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
838 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
839 $link['class'] .= ' mediafile mf_'.$class;
842 if($hash) $link['url'] .= '#'.$hash;
845 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
846 else $this->doc .= $this->_formatLink($link);
850 * Renders an RSS feed
852 * @author Andreas Gohr <andi@splitbrain.org>
854 function rss ($url,$params){
858 require_once(DOKU_INC.'inc/FeedParser.php');
859 $feed = new FeedParser();
860 $feed->set_feed_url($url);
862 //disable warning while fetching
863 if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
865 if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
867 //decide on start and end
868 if($params['reverse']){
870 $start = $feed->get_item_quantity()-1;
871 $end = $start - ($params['max']);
872 $end = ($end < -1) ? -1 : $end;
876 $end = $feed->get_item_quantity();
877 $end = ($end > $params['max']) ? $params['max'] : $end;;
880 $this->doc .= '<ul class="rss">';
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();
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()));
893 $this->doc .= ' '.$item->get_title();
895 if($params['author']){
896 $author = $item->get_author(0);
898 $name = $author->get_name();
899 if(!$name) $name = $author->get_email();
900 if($name) $this->doc .= ' '.$lang['by'].' '.$name;
904 $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
906 if($params['details']){
907 $this->doc .= '<div class="detail">';
909 $this->doc .= $item->get_description();
911 $this->doc .= strip_tags($item->get_description());
913 $this->doc .= '</div>';
916 $this->doc .= '</div></li>';
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).'-->';
925 $this->doc .= '</div></li>';
927 $this->doc .= '</ul>';
930 // $numrows not yet implemented
931 function table_open($maxcols = null, $numrows = null, $pos = null){
933 // initialize the row counter used for classes
934 $this->_counter['row_counter'] = 0;
937 $class .= ' ' . $this->startSectionEdit($pos, 'table');
939 $this->doc .= '<div class="' . $class . '"><table class="inline">' .
943 function table_close($pos = null){
944 $this->doc .= '</table></div>'.DOKU_LF;
946 $this->finishSectionEdit($pos);
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;
957 function tablerow_close(){
958 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
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';
967 $this->doc .= '<th ' . $class;
968 if ( $colspan > 1 ) {
969 $this->_counter['cell_counter'] += $colspan-1;
970 $this->doc .= ' colspan="'.$colspan.'"';
972 if ( $rowspan > 1 ) {
973 $this->doc .= ' rowspan="'.$rowspan.'"';
978 function tableheader_close(){
979 $this->doc .= '</th>';
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';
988 $this->doc .= '<td '.$class;
989 if ( $colspan > 1 ) {
990 $this->_counter['cell_counter'] += $colspan-1;
991 $this->doc .= ' colspan="'.$colspan.'"';
993 if ( $rowspan > 1 ) {
994 $this->doc .= ' rowspan="'.$rowspan.'"';
999 function tablecell_close(){
1000 $this->doc .= '</td>';
1003 //----------------------------------------------------------
1009 * Assembles all parts defined in $link returns HTML for the link
1011 * @author Andreas Gohr <andi@splitbrain.org>
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('&','&',$link['url']);
1017 $link['url'] = str_replace('&amp;','&',$link['url']);
1019 //remove double encodings in titles
1020 $link['title'] = str_replace('&amp;','&',$link['title']);
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('>'=>'>','<'=>'<','"'=>'"'));
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'];
1037 $ret .= $link['name'];
1039 $ret .= $link['suf'];
1044 * Renders internal and external media
1046 * @author Andreas Gohr <andi@splitbrain.org>
1048 function _media ($src, $title=NULL, $align=NULL, $width=NULL,
1049 $height=NULL, $cache=NULL, $render = true) {
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();
1064 $title = $this->_xmlEntities($cap);
1068 // if the picture is not supposed to be rendered
1069 // return the title of the picture
1071 // just show the sourcename
1072 $title = $this->_xmlEntities(utf8_basename(noNS($src)));
1077 $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
1078 $ret .= ' class="media'.$align.'"';
1081 $ret .= ' title="' . $title . '"';
1082 $ret .= ' alt="' . $title .'"';
1087 if ( !is_null($width) )
1088 $ret .= ' width="'.$this->_xmlEntities($width).'"';
1090 if ( !is_null($height) )
1091 $ret .= ' height="'.$this->_xmlEntities($height).'"';
1095 }elseif($mime == 'application/x-shockwave-flash'){
1097 // if the flash is not supposed to be rendered
1098 // return the title of the flash
1100 // just show the sourcename
1101 $title = utf8_basename(noNS($src));
1103 return $this->_xmlEntities($title);
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'),
1114 $this->_xmlEntities($title));
1116 // well at least we have a title to display
1117 $ret .= $this->_xmlEntities($title);
1119 // just show the sourcename
1120 $ret .= $this->_xmlEntities(utf8_basename(noNS($src)));
1126 function _xmlEntities($string) {
1127 return htmlspecialchars($string,ENT_QUOTES,'UTF-8');
1131 * Creates a linkid from a headline
1133 * @param string $title The headline title
1134 * @param boolean $create Create a new unique ID?
1135 * @author Andreas Gohr <andi@splitbrain.org>
1137 function _headerToLink($title,$create=false) {
1139 return sectionID($title,$this->headers);
1142 return sectionID($title,$check);
1147 * Construct a title and handle images in titles
1149 * @author Harry Fuecks <hfuecks@gmail.com>
1151 function _getLinkTitle($title, $default, & $isImage, $id=NULL, $linktype='content') {
1155 if ( is_array($title) ) {
1157 return $this->_imageTitle($title);
1158 } elseif ( is_null($title) || trim($title)=='') {
1159 if (useHeading($linktype) && $id) {
1160 $heading = p_get_first_heading($id);
1162 return $this->_xmlEntities($heading);
1165 return $this->_xmlEntities($default);
1167 return $this->_xmlEntities($title);
1172 * Returns an HTML code for images used in link titles
1174 * @todo Resolve namespace on internal images
1175 * @author Andreas Gohr <andi@splitbrain.org>
1177 function _imageTitle($img) {
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);
1187 return $this->_media($img['src'],
1196 * _getMediaLinkConf is a helperfunction to internalmedia() and externalmedia()
1197 * which returns a basic link to a media.
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
1210 function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render)
1215 $link['class'] = 'media';
1216 $link['style'] = '';
1220 $link['target'] = $conf['target']['media'];
1221 $link['title'] = $this->_xmlEntities($src);
1222 $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render);
1230 //Setup VIM: ex: et ts=4 :