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 private $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.'" name="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).'" name="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 .= '><a name="'.$hid.'" id="'.$hid.'">';
185 $this->doc .= $this->_xmlEntities($text);
186 $this->doc .= "</a></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.'" name="fnt__'.$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>';
342 function listitem_close() {
343 $this->doc .= '</li>'.DOKU_LF;
346 function listcontent_open() {
350 function listcontent_close() {
351 $this->doc .= ''.DOKU_LF;
354 function unformatted($text) {
355 $this->doc .= $this->_xmlEntities($text);
359 * Execute PHP code if allowed
361 * @param string $wrapper html element to wrap result if $conf['phpok'] is okff
363 * @author Andreas Gohr <andi@splitbrain.org>
365 function php($text, $wrapper='code') {
371 $this->doc .= ob_get_contents();
374 $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper);
378 function phpblock($text) {
379 $this->php($text, 'pre');
383 * Insert HTML if allowed
385 * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff
387 * @author Andreas Gohr <andi@splitbrain.org>
389 function html($text, $wrapper='code') {
395 $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper);
399 function htmlblock($text) {
400 $this->html($text, 'pre');
403 function quote_open() {
404 $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
407 function quote_close() {
408 $this->doc .= '</div></blockquote>'.DOKU_LF;
411 function preformatted($text) {
412 $this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text),"\n\r") . '</pre>'. DOKU_LF;
415 function file($text, $language=null, $filename=null) {
416 $this->_highlight('file',$text,$language,$filename);
419 function code($text, $language=null, $filename=null) {
420 $this->_highlight('code',$text,$language,$filename);
424 * Use GeSHi to highlight language syntax in code and file blocks
426 * @author Andreas Gohr <andi@splitbrain.org>
428 function _highlight($type, $text, $language=null, $filename=null) {
435 list($ext) = mimetype($filename,false);
436 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
437 $class = 'mediafile mf_'.$class;
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>';
445 if ($text{0} == "\n") {
446 $text = substr($text, 1);
448 if (substr($text, -1) == "\n") {
449 $text = substr($text, 0, -1);
452 if ( is_null($language) ) {
453 $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
455 $class = 'code'; //we always need the code class to make the syntax highlighting apply
456 if($type != 'code') $class .= ' '.$type;
458 $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '').'</pre>'.DOKU_LF;
462 $this->doc .= '</dd></dl>'.DOKU_LF;
468 function acronym($acronym) {
470 if ( array_key_exists($acronym, $this->acronyms) ) {
472 $title = $this->_xmlEntities($this->acronyms[$acronym]);
474 $this->doc .= '<acronym title="'.$title
475 .'">'.$this->_xmlEntities($acronym).'</acronym>';
478 $this->doc .= $this->_xmlEntities($acronym);
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).'" />';
489 $this->doc .= $this->_xmlEntities($smiley);
495 function wordblock($word) {
496 if ( array_key_exists($word, $this->badwords) ) {
497 $this->doc .= '** BLEEP **';
499 $this->doc .= $this->_xmlEntities($word);
504 function entity($entity) {
505 if ( array_key_exists($entity, $this->entities) ) {
506 $this->doc .= $this->entities[$entity];
508 $this->doc .= $this->_xmlEntities($entity);
512 function multiplyentity($x, $y) {
513 $this->doc .= "$x×$y";
516 function singlequoteopening() {
518 $this->doc .= $lang['singlequoteopening'];
521 function singlequoteclosing() {
523 $this->doc .= $lang['singlequoteclosing'];
526 function apostrophe() {
528 $this->doc .= $lang['apostrophe'];
531 function doublequoteopening() {
533 $this->doc .= $lang['doublequoteopening'];
536 function doublequoteclosing() {
538 $this->doc .= $lang['doublequoteclosing'];
543 function camelcaselink($link) {
544 $this->internallink($link,$link);
548 function locallink($hash, $name = NULL){
550 $name = $this->_getLinkTitle($name, $hash, $isImage);
551 $hash = $this->_headerToLink($hash);
552 $title = $ID.' ↵';
553 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
555 $this->doc .= '</a>';
559 * Render an internal Wiki Link
561 * $search,$returnonly & $linktype are not for the renderer but are used
562 * elsewhere - no need to implement them in other renderers
564 * @author Andreas Gohr <andi@splitbrain.org>
566 function internallink($id, $name = NULL, $search=NULL,$returnonly=false,$linktype='content') {
571 $parts = explode('?', $id, 2);
572 if (count($parts) === 2) {
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)
585 // default name is based on $id as given
586 $default = $this->_simpleTitle($id);
588 // now first resolve and clean up the $id
589 resolve_pageid(getNS($ID),$id,$exists);
591 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
597 $link['rel']='nofollow';
604 list($id,$hash) = explode('#',$id,2);
605 if(!empty($hash)) $hash = $this->_headerToLink($hash);
607 //prepare for formating
608 $link['target'] = $conf['target']['wiki'];
612 // highlight link to current page
614 $link['pre'] = '<span class="curid">';
615 $link['suf'] = '</span>';
618 $link['class'] = $class;
619 $link['url'] = wl($id, $params);
620 $link['name'] = $name;
621 $link['title'] = $id;
624 ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&';
625 if(is_array($search)){
626 $search = array_map('rawurlencode',$search);
627 $link['url'] .= 's[]='.join('&s[]=',$search);
629 $link['url'] .= 's='.rawurlencode($search);
634 if($hash) $link['url'].='#'.$hash;
638 return $this->_formatLink($link);
640 $this->doc .= $this->_formatLink($link);
644 function externallink($url, $name = NULL) {
647 $name = $this->_getLinkTitle($name, $url, $isImage);
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 = '';
655 // is there still an URL?
668 //prepare for formating
669 $link['target'] = $conf['target']['extern'];
674 $link['class'] = $class;
677 $link['name'] = $name;
678 $link['title'] = $this->_xmlEntities($url);
679 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
682 $this->doc .= $this->_formatLink($link);
687 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
691 $link['target'] = $conf['target']['interwiki'];
695 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage);
698 $url = $this->_resolveInterWiki($wikiName,$wikiUri);
701 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
702 $link['class'] = "interwiki urlextern w_$class";
704 $link['class'] = 'media';
707 //do we stay at the same server? Use local target
708 if( strpos($url,DOKU_URL) === 0 ){
709 $link['target'] = $conf['target']['wiki'];
713 $link['title'] = htmlspecialchars($link['url']);
716 $this->doc .= $this->_formatLink($link);
721 function windowssharelink($url, $name = NULL) {
725 $link['target'] = $conf['target']['windows'];
730 $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
732 $link['class'] = 'windows';
734 $link['class'] = 'media';
738 $link['title'] = $this->_xmlEntities($url);
739 $url = str_replace('\\','/',$url);
740 $url = 'file:///'.$url;
744 $this->doc .= $this->_formatLink($link);
747 function emaillink($address, $name = NULL) {
751 $link['target'] = '';
757 $name = $this->_getLinkTitle($name, '', $isImage);
759 $link['class']='mail';
761 $link['class']='media';
764 $address = $this->_xmlEntities($address);
765 $address = obfuscate($address);
772 if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
774 $link['url'] = 'mailto:'.$address;
775 $link['name'] = $name;
776 $link['title'] = $title;
779 $this->doc .= $this->_formatLink($link);
782 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
783 $height=NULL, $cache=NULL, $linking=NULL) {
785 list($src,$hash) = explode('#',$src,2);
786 resolve_mediaid(getNS($ID),$src, $exists);
789 $render = ($linking == 'linkonly') ? false : true;
790 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
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
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);
805 if($hash) $link['url'] .= '#'.$hash;
807 //markup non existing files
809 $link['class'] .= ' wikilink2';
813 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
814 else $this->doc .= $this->_formatLink($link);
817 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
818 $height=NULL, $cache=NULL, $linking=NULL) {
819 list($src,$hash) = explode('#',$src,2);
821 $render = ($linking == 'linkonly') ? false : true;
822 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
824 $link['url'] = ml($src,array('cache'=>$cache));
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
835 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
836 $link['class'] .= ' mediafile mf_'.$class;
839 if($hash) $link['url'] .= '#'.$hash;
842 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
843 else $this->doc .= $this->_formatLink($link);
847 * Renders an RSS feed
849 * @author Andreas Gohr <andi@splitbrain.org>
851 function rss ($url,$params){
855 require_once(DOKU_INC.'inc/FeedParser.php');
856 $feed = new FeedParser();
857 $feed->set_feed_url($url);
859 //disable warning while fetching
860 if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
862 if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
864 //decide on start and end
865 if($params['reverse']){
867 $start = $feed->get_item_quantity()-1;
868 $end = $start - ($params['max']);
869 $end = ($end < -1) ? -1 : $end;
873 $end = $feed->get_item_quantity();
874 $end = ($end > $params['max']) ? $params['max'] : $end;;
877 $this->doc .= '<ul class="rss">';
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();
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()));
890 $this->doc .= ' '.$item->get_title();
892 if($params['author']){
893 $author = $item->get_author(0);
895 $name = $author->get_name();
896 if(!$name) $name = $author->get_email();
897 if($name) $this->doc .= ' '.$lang['by'].' '.$name;
901 $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
903 if($params['details']){
904 $this->doc .= '<div class="detail">';
906 $this->doc .= $item->get_description();
908 $this->doc .= strip_tags($item->get_description());
910 $this->doc .= '</div>';
913 $this->doc .= '</div></li>';
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).'-->';
922 $this->doc .= '</div></li>';
924 $this->doc .= '</ul>';
927 // $numrows not yet implemented
928 function table_open($maxcols = null, $numrows = null, $pos = null){
930 // initialize the row counter used for classes
931 $this->_counter['row_counter'] = 0;
934 $class .= ' ' . $this->startSectionEdit($pos, 'table');
936 $this->doc .= '<div class="' . $class . '"><table class="inline">' .
940 function table_close($pos = null){
941 $this->doc .= '</table></div>'.DOKU_LF;
943 $this->finishSectionEdit($pos);
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;
954 function tablerow_close(){
955 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
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';
964 $this->doc .= '<th ' . $class;
965 if ( $colspan > 1 ) {
966 $this->_counter['cell_counter'] += $colspan-1;
967 $this->doc .= ' colspan="'.$colspan.'"';
969 if ( $rowspan > 1 ) {
970 $this->doc .= ' rowspan="'.$rowspan.'"';
975 function tableheader_close(){
976 $this->doc .= '</th>';
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';
985 $this->doc .= '<td '.$class;
986 if ( $colspan > 1 ) {
987 $this->_counter['cell_counter'] += $colspan-1;
988 $this->doc .= ' colspan="'.$colspan.'"';
990 if ( $rowspan > 1 ) {
991 $this->doc .= ' rowspan="'.$rowspan.'"';
996 function tablecell_close(){
997 $this->doc .= '</td>';
1000 //----------------------------------------------------------
1006 * Assembles all parts defined in $link returns HTML for the link
1008 * @author Andreas Gohr <andi@splitbrain.org>
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('&','&',$link['url']);
1014 $link['url'] = str_replace('&amp;','&',$link['url']);
1016 //remove double encodings in titles
1017 $link['title'] = str_replace('&amp;','&',$link['title']);
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('>'=>'>','<'=>'<','"'=>'"'));
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'];
1034 $ret .= $link['name'];
1036 $ret .= $link['suf'];
1041 * Renders internal and external media
1043 * @author Andreas Gohr <andi@splitbrain.org>
1045 function _media ($src, $title=NULL, $align=NULL, $width=NULL,
1046 $height=NULL, $cache=NULL, $render = true) {
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();
1061 $title = $this->_xmlEntities($cap);
1065 // if the picture is not supposed to be rendered
1066 // return the title of the picture
1068 // just show the sourcename
1069 $title = $this->_xmlEntities(basename(noNS($src)));
1074 $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
1075 $ret .= ' class="media'.$align.'"';
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"';
1082 $ret .= ' title="' . $title . '"';
1083 $ret .= ' alt="' . $title .'"';
1088 if ( !is_null($width) )
1089 $ret .= ' width="'.$this->_xmlEntities($width).'"';
1091 if ( !is_null($height) )
1092 $ret .= ' height="'.$this->_xmlEntities($height).'"';
1096 }elseif($mime == 'application/x-shockwave-flash'){
1098 // if the flash is not supposed to be rendered
1099 // return the title of the flash
1101 // just show the sourcename
1102 $title = basename(noNS($src));
1104 return $this->_xmlEntities($title);
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'),
1115 $this->_xmlEntities($title));
1117 // well at least we have a title to display
1118 $ret .= $this->_xmlEntities($title);
1120 // just show the sourcename
1121 $ret .= $this->_xmlEntities(basename(noNS($src)));
1127 function _xmlEntities($string) {
1128 return htmlspecialchars($string,ENT_QUOTES,'UTF-8');
1132 * Creates a linkid from a headline
1134 * @param string $title The headline title
1135 * @param boolean $create Create a new unique ID?
1136 * @author Andreas Gohr <andi@splitbrain.org>
1138 function _headerToLink($title,$create=false) {
1140 return sectionID($title,$this->headers);
1143 return sectionID($title,$check);
1148 * Construct a title and handle images in titles
1150 * @author Harry Fuecks <hfuecks@gmail.com>
1152 function _getLinkTitle($title, $default, & $isImage, $id=NULL, $linktype='content') {
1156 if ( is_array($title) ) {
1158 return $this->_imageTitle($title);
1159 } elseif ( is_null($title) || trim($title)=='') {
1160 if (useHeading($linktype) && $id) {
1161 $heading = p_get_first_heading($id);
1163 return $this->_xmlEntities($heading);
1166 return $this->_xmlEntities($default);
1168 return $this->_xmlEntities($title);
1173 * Returns an HTML code for images used in link titles
1175 * @todo Resolve namespace on internal images
1176 * @author Andreas Gohr <andi@splitbrain.org>
1178 function _imageTitle($img) {
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);
1188 return $this->_media($img['src'],
1197 * _getMediaLinkConf is a helperfunction to internalmedia() and externalmedia()
1198 * which returns a basic link to a media.
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
1211 function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render)
1216 $link['class'] = 'media';
1217 $link['style'] = '';
1221 $link['target'] = $conf['target']['media'];
1222 $link['title'] = $this->_xmlEntities($src);
1223 $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render);
1231 //Setup VIM: ex: et ts=4 :