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