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 9if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 10 11if ( !defined('DOKU_LF') ) { 12 // Some whitespace to help View > Source 13 define ('DOKU_LF',"\n"); 14} 15 16if ( !defined('DOKU_TAB') ) { 17 // Some whitespace to help View > Source 18 define ('DOKU_TAB',"\t"); 19} 20 21require_once DOKU_INC . 'inc/parser/renderer.php'; 22require_once DOKU_INC . 'inc/pluginutils.php'; 23 24/** 25 * The Renderer 26 */ 27class Doku_Renderer_xhtml extends Doku_Renderer { 28 29 var $doc = ''; 30 31 var $headers = array(); 32 33 var $footnotes = array(); 34 35 var $acronyms = array(); 36 var $smileys = array(); 37 var $badwords = array(); 38 var $entities = array(); 39 var $interwiki = array(); 40 41 var $lastsec = 0; 42 43 var $store = ''; 44 45 function document_start() { 46 } 47 48 function document_end() { 49 // add button for last section if any and more than one 50 if($this->lastsec > 1) $this->_secedit($this->lastsec,''); 51 52 if ( count ($this->footnotes) > 0 ) { 53 $this->doc .= '<div class="footnotes">'.DOKU_LF; 54 foreach ( $this->footnotes as $footnote ) { 55 $this->doc .= $footnote; 56 } 57 $this->doc .= '</div>'.DOKU_LF; 58 } 59 } 60 61 //handles plugin rendering 62 function plugin($name,$data){ 63 $plugin = null; 64 if(plugin_load('syntax',$name,$plugin)){ 65 $plugin->render('xhtml',$this,$data); 66 } 67 } 68 69 function toc_open() { 70 global $lang; 71 $this->doc .= '<div class="toc">'.DOKU_LF; 72 $this->doc .= '<div class="tocheader">'; 73 $this->doc .= ' <script type="text/javascript">showTocToggle("+","-")</script>'; 74 $this->doc .= $lang['toc']; 75 $this->doc .= '</div>'.DOKU_LF; 76 $this->doc .= '<div id="tocinside">'.DOKU_LF; 77 } 78 79 function tocbranch_open($level) { 80 $this->doc .= '<ul class="toc">'.DOKU_LF; 81 } 82 83 function tocitem_open($level, $empty = FALSE) { 84 if ( !$empty ) { 85 $this->doc .= '<li class="level'.$level.'">'; 86 } else { 87 $this->doc .= '<li class="clear">'; 88 } 89 } 90 91 function tocelement($level, $title) { 92 $this->doc .= '<span class="li"><a href="#'.$this->_headerToLink($title).'" class="toc">'; 93 $this->doc .= $this->_xmlEntities($title); 94 $this->doc .= '</a></span>'; 95 } 96 97 function tocitem_close($level) { 98 $this->doc .= '</li>'.DOKU_LF; 99 } 100 101 function tocbranch_close($level) { 102 $this->doc .= '</ul>'.DOKU_LF; 103 } 104 105 function toc_close() { 106 $this->doc .= '</div>'.DOKU_LF.'</div>'.DOKU_LF; 107 } 108 109 function header($text, $level, $pos) { 110 global $conf; 111 //handle section editing 112 if($level <= $conf['maxseclevel']){ 113 // add button for last section if any 114 if($this->lastsec) $this->_secedit($this->lastsec,$pos-1); 115 // remember current position 116 $this->lastsec = $pos; 117 } 118 119 $this->doc .= DOKU_LF.'<a name="'.$this->_headerToLink($text).'"></a><h'.$level.'>'; 120 $this->doc .= $this->_xmlEntities($text); 121 $this->doc .= "</h$level>".DOKU_LF; 122 } 123 124 function section_open($level) { 125 $this->doc .= "<div class=\"level$level\">".DOKU_LF; 126 } 127 128 function section_close() { 129 $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 130 } 131 132 function cdata($text) { 133 $this->doc .= $this->_xmlEntities($text); 134 } 135 136 function p_open() { 137 $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 138 } 139 140 function p_close() { 141 $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 142 } 143 144 function linebreak() { 145 $this->doc .= '<br/>'.DOKU_LF; 146 } 147 148 function hr() { 149 $this->doc .= '<hr noshade="noshade" size="1" />'.DOKU_LF; 150 } 151 152 function strong_open() { 153 $this->doc .= '<strong>'; 154 } 155 156 function strong_close() { 157 $this->doc .= '</strong>'; 158 } 159 160 function emphasis_open() { 161 $this->doc .= '<em>'; 162 } 163 164 function emphasis_close() { 165 $this->doc .= '</em>'; 166 } 167 168 function underline_open() { 169 $this->doc .= '<u>'; 170 } 171 172 function underline_close() { 173 $this->doc .= '</u>'; 174 } 175 176 function monospace_open() { 177 $this->doc .= '<code>'; 178 } 179 180 function monospace_close() { 181 $this->doc .= '</code>'; 182 } 183 184 function subscript_open() { 185 $this->doc .= '<sub>'; 186 } 187 188 function subscript_close() { 189 $this->doc .= '</sub>'; 190 } 191 192 function superscript_open() { 193 $this->doc .= '<sup>'; 194 } 195 196 function superscript_close() { 197 $this->doc .= '</sup>'; 198 } 199 200 function deleted_open() { 201 $this->doc .= '<del>'; 202 } 203 204 function deleted_close() { 205 $this->doc .= '</del>'; 206 } 207 208 /** 209 * Callback for footnote start syntax 210 * 211 * All following content will go to the footnote instead of 212 * the document. To achive this the previous rendered content 213 * is moved to $store and $doc is cleared 214 * 215 * @author Andreas Gohr <andi@splitbrain.org> 216 */ 217 function footnote_open() { 218 #$id = $this->_newFootnoteId(); 219 $id = count($this->footnotes)+1; 220 $this->doc .= '<a href="#fn'.$id.'" name="fnt'.$id.'" class="fn_top">'.$id.')</a>'; 221 222 // move current content to store and record footnote 223 $this->store = $this->doc; 224 $this->doc = ''; 225 226 $this->doc .= '<div class="fn">'; 227 $this->doc .= '<a href="#fnt'.$id.'" name="fn'.$id.'" class="fn_bot">'; 228 $this->doc .= $id.')</a> '.DOKU_LF; 229 } 230 231 /** 232 * Callback for footnote end syntax 233 * 234 * All rendered content is moved to the $footnotes array and the old 235 * content is restored from $store again 236 * 237 * @author Andreas Gohr 238 */ 239 function footnote_close() { 240 $this->doc .= '</div>' . DOKU_LF; 241 242 // put recorded footnote into the stack and restore old content 243 $this->footnotes[count($this->footnotes)] = $this->doc; 244 $this->doc = $this->store; 245 $this->store = ''; 246 } 247 248 function listu_open() { 249 $this->doc .= '<ul>'.DOKU_LF; 250 } 251 252 function listu_close() { 253 $this->doc .= '</ul>'.DOKU_LF; 254 } 255 256 function listo_open() { 257 $this->doc .= '<ol>'.DOKU_LF; 258 } 259 260 function listo_close() { 261 $this->doc .= '</ol>'.DOKU_LF; 262 } 263 264 function listitem_open($level) { 265 $this->doc .= '<li class="level'.$level.'">'; 266 } 267 268 function listitem_close() { 269 $this->doc .= '</li>'.DOKU_LF; 270 } 271 272 function listcontent_open() { 273 $this->doc .= '<div class="li">'; 274 } 275 276 function listcontent_close() { 277 $this->doc .= '</div>'.DOKU_LF; 278 } 279 280 function unformatted($text) { 281 $this->doc .= $this->_xmlEntities($text); 282 } 283 284 /** 285 * Execute PHP code if allowed 286 * 287 * @author Andreas Gohr <andi@splitbrain.org> 288 */ 289 function php($text) { 290 global $conf; 291 if($conf['phpok']){ 292 ob_start(); 293 eval($text); 294 $this->doc .= ob_get_contents(); 295 ob_end_clean(); 296 }else{ 297 $this->file($text); 298 } 299 } 300 301 /** 302 * Insert HTML if allowed 303 * 304 * @author Andreas Gohr <andi@splitbrain.org> 305 */ 306 function html($text) { 307 global $conf; 308 if($conf['htmlok']){ 309 $this->doc .= $text; 310 }else{ 311 $this->file($text); 312 } 313 } 314 315 function preformatted($text) { 316 $this->doc .= '<pre class="code">' . $this->_xmlEntities($text) . '</pre>'. DOKU_LF; 317 } 318 319 function file($text) { 320 $this->doc .= '<pre class="file">' . $this->_xmlEntities($text). '</pre>'. DOKU_LF; 321 } 322 323 function quote_open() { 324 $this->doc .= '<blockquote>'.DOKU_LF; 325 } 326 327 function quote_close() { 328 $this->doc .= '</blockquote>'.DOKU_LF; 329 } 330 331 /** 332 * Callback for code text 333 * 334 * Uses GeSHi to highlight language syntax 335 * 336 * @author Andreas Gohr <andi@splitbrain.org> 337 */ 338 function code($text, $language = NULL) { 339 global $conf; 340 341 if ( is_null($language) ) { 342 $this->preformatted($text); 343 } else { 344 //strip leading blank line 345 $text = preg_replace('/^\s*?\n/','',$text); 346 // Handle with Geshi here 347 require_once(DOKU_INC . 'inc/geshi.php'); 348 $geshi = new GeSHi($text, strtolower($language), DOKU_INC . 'inc/geshi'); 349 $geshi->set_encoding('utf-8'); 350 $geshi->enable_classes(); 351 $geshi->set_header_type(GESHI_HEADER_PRE); 352 $geshi->set_overall_class("code $language"); 353 $geshi->set_link_target($conf['target']['extern']); 354 355 $text = $geshi->parse_code(); 356 $this->doc .= $text; 357 } 358 } 359 360 function acronym($acronym) { 361 362 if ( array_key_exists($acronym, $this->acronyms) ) { 363 364 $title = $this->_xmlEntities($this->acronyms[$acronym]); 365 366 $this->doc .= '<acronym title="'.$title 367 .'">'.$this->_xmlEntities($acronym).'</acronym>'; 368 369 } else { 370 $this->doc .= $this->_xmlEntities($acronym); 371 } 372 } 373 374 function smiley($smiley) { 375 if ( array_key_exists($smiley, $this->smileys) ) { 376 $title = $this->_xmlEntities($this->smileys[$smiley]); 377 $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley]. 378 '" align="middle" alt="'. 379 $this->_xmlEntities($smiley).'" />'; 380 } else { 381 $this->doc .= $this->_xmlEntities($smiley); 382 } 383 } 384 385 /* 386 * not used 387 function wordblock($word) { 388 if ( array_key_exists($word, $this->badwords) ) { 389 $this->doc .= '** BLEEP **'; 390 } else { 391 $this->doc .= $this->_xmlEntities($word); 392 } 393 } 394 */ 395 396 function entity($entity) { 397 if ( array_key_exists($entity, $this->entities) ) { 398 $this->doc .= $this->entities[$entity]; 399 } else { 400 $this->doc .= $this->_xmlEntities($entity); 401 } 402 } 403 404 function multiplyentity($x, $y) { 405 $this->doc .= "$x×$y"; 406 } 407 408 function singlequoteopening() { 409 $this->doc .= "‘"; 410 } 411 412 function singlequoteclosing() { 413 $this->doc .= "’"; 414 } 415 416 function doublequoteopening() { 417 $this->doc .= "“"; 418 } 419 420 function doublequoteclosing() { 421 $this->doc .= "”"; 422 } 423 424 /** 425 */ 426 function camelcaselink($link) { 427 $this->internallink($link,$link); 428 } 429 430 431 function locallink($hash, $name = NULL){ 432 global $ID; 433 $name = $this->_getLinkTitle($name, $hash, $isImage); 434 $hash = $this->_headerToLink($hash); 435 $title = $ID.' ↵'; 436 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 437 $this->doc .= $name; 438 $this->doc .= '</a>'; 439 } 440 441 /** 442 * Render an internal Wiki Link 443 * 444 * $search and $returnonly are not for the renderer but are used 445 * elsewhere - no need to implement them in other renderers 446 * 447 * @author Andreas Gohr <andi@splitbrain.org> 448 */ 449 function internallink($id, $name = NULL, $search=NULL,$returnonly=false) { 450 global $conf; 451 global $ID; 452 // default name is based on $id as given 453 $default = $this->_simpleTitle($id); 454 // now first resolve and clean up the $id 455 resolve_pageid(getNS($ID),$id,$exists); 456 $name = $this->_getLinkTitle($name, $default, $isImage, $id); 457 if ( !$isImage ) { 458 if ( $exists ) { 459 $class='wikilink1'; 460 } else { 461 $class='wikilink2'; 462 } 463 } else { 464 $class='media'; 465 } 466 467 //keep hash anchor 468 list($id,$hash) = split('#',$id,2); 469 470 //prepare for formating 471 $link['target'] = $conf['target']['wiki']; 472 $link['style'] = ''; 473 $link['pre'] = ''; 474 $link['suf'] = ''; 475 // highlight link to current page 476 if ($id == $ID) { 477 $link['pre'] = '<span class="curid">'; 478 $link['suf'] = '</span>'; 479 } 480 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 481 $link['class'] = $class; 482 $link['url'] = wl($id); 483 $link['name'] = $name; 484 $link['title'] = $id; 485 //add search string 486 if($search){ 487 ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&s='; 488 $link['url'] .= urlencode($search); 489 } 490 491 //keep hash 492 if($hash) $link['url'].='#'.$hash; 493 494 //output formatted 495 if($returnonly){ 496 return $this->_formatLink($link); 497 }else{ 498 $this->doc .= $this->_formatLink($link); 499 } 500 } 501 502 function externallink($url, $name = NULL) { 503 global $conf; 504 505 $name = $this->_getLinkTitle($name, $url, $isImage); 506 507 // add protocol on simple short URLs 508 if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')) $url = 'ftp://'.$url; 509 if(substr($url,0,3) == 'www') $url = 'http://'.$url; 510 511 if ( !$isImage ) { 512 $class='urlextern'; 513 } else { 514 $class='media'; 515 } 516 517 //prepare for formating 518 $link['target'] = $conf['target']['extern']; 519 $link['style'] = ''; 520 $link['pre'] = ''; 521 $link['suf'] = ''; 522 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 523 $link['class'] = $class; 524 $link['url'] = $url; 525 $link['name'] = $name; 526 $link['title'] = $this->_xmlEntities($url); 527 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 528 529 //output formatted 530 $this->doc .= $this->_formatLink($link); 531 } 532 533 /** 534 */ 535 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { 536 global $conf; 537 538 $link = array(); 539 $link['target'] = $conf['target']['interwiki']; 540 $link['pre'] = ''; 541 $link['suf'] = ''; 542 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 543 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 544 545 if ( !$isImage ) { 546 $link['class'] = 'interwiki'; 547 } else { 548 $link['class'] = 'media'; 549 } 550 551 //get interwiki URL 552 if ( isset($this->interwiki[$wikiName]) ) { 553 $url = $this->interwiki[$wikiName]; 554 } else { 555 // Default to Google I'm feeling lucky 556 $url = 'http://www.google.com/search?q={URL}&btnI=lucky'; 557 $wikiName = 'go'; 558 } 559 560 if(!$isImage){ 561 //if ico exists set additional style 562 if(@file_exists(DOKU_INC.'lib/images/interwiki/'.$wikiName.'.png')){ 563 $link['style']='background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$wikiName.'.png)'; 564 }elseif(@file_exists(DOKU_INC.'lib/images/interwiki/'.$wikiName.'.gif')){ 565 $link['style']='background-image: url('.DOKU_BASE.'lib/images/interwiki/'.$wikiName.'.gif)'; 566 } 567 } 568 569 //do we stay at the same server? Use local target 570 if( strpos($url,DOKU_URL) === 0 ){ 571 $link['target'] = $conf['target']['wiki']; 572 } 573 574 //replace placeholder 575 if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#',$url)){ 576 //use placeholders 577 $url = str_replace('{URL}',urlencode($wikiUri),$url); 578 $url = str_replace('{NAME}',$wikiUri,$url); 579 $parsed = parse_url($wikiUri); 580 if(!$parsed['port']) $parsed['port'] = 80; 581 $url = str_replace('{SCHEME}',$parsed['scheme'],$url); 582 $url = str_replace('{HOST}',$parsed['host'],$url); 583 $url = str_replace('{PORT}',$parsed['port'],$url); 584 $url = str_replace('{PATH}',$parsed['path'],$url); 585 $url = str_replace('{QUERY}',$parsed['query'],$url); 586 $link['url'] = $url; 587 }else{ 588 //default 589 $link['url'] = $url.urlencode($wikiUri); 590 } 591 592 $link['title'] = htmlspecialchars($link['url']); 593 594 //output formatted 595 $this->doc .= $this->_formatLink($link); 596 } 597 598 /** 599 */ 600 function windowssharelink($url, $name = NULL) { 601 global $conf; 602 global $lang; 603 //simple setup 604 $link['target'] = $conf['target']['windows']; 605 $link['pre'] = ''; 606 $link['suf'] = ''; 607 $link['style'] = ''; 608 //Display error on browsers other than IE 609 $link['more'] = 'onclick="if(document.all == null){alert(\''. 610 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES). 611 '\');}" onkeypress="if(document.all == null){alert(\''. 612 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"'; 613 614 $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 615 if ( !$isImage ) { 616 $link['class'] = 'windows'; 617 } else { 618 $link['class'] = 'media'; 619 } 620 621 622 $link['title'] = $this->_xmlEntities($url); 623 $url = str_replace('\\','/',$url); 624 $url = 'file:///'.$url; 625 $link['url'] = $url; 626 627 //output formatted 628 $this->doc .= $this->_formatLink($link); 629 } 630 631 function emaillink($address, $name = NULL) { 632 global $conf; 633 //simple setup 634 $link = array(); 635 $link['target'] = ''; 636 $link['pre'] = ''; 637 $link['suf'] = ''; 638 $link['style'] = ''; 639 $link['more'] = ''; 640 641 //we just test for image here - we need to encode the title our self 642 $this->_getLinkTitle($name, $address, $isImage); 643 if ( !$isImage ) { 644 $link['class']='mail'; 645 } else { 646 $link['class']='media'; 647 } 648 649 //shields up 650 if($conf['mailguard']=='visible'){ 651 //the mail name gets some visible encoding 652 $address = str_replace('@',' [at] ',$address); 653 $address = str_replace('.',' [dot] ',$address); 654 $address = str_replace('-',' [dash] ',$address); 655 656 $title = $this->_xmlEntities($address); 657 if(empty($name)){ 658 $name = $this->_xmlEntities($address); 659 }else{ 660 $name = $this->_xmlEntities($name); 661 } 662 }elseif($conf['mailguard']=='hex'){ 663 //encode every char to a hex entity 664 for ($x=0; $x < strlen($address); $x++) { 665 $encode .= '&#x' . bin2hex($address[$x]).';'; 666 } 667 $address = $encode; 668 $title = $encode; 669 if(empty($name)){ 670 $name = $encode; 671 }else{ 672 $name = $this->_xmlEntities($name); 673 } 674 }else{ 675 //keep address as is 676 $title = $this->_xmlEntities($address); 677 if(empty($name)){ 678 $name = $this->_xmlEntities($address); 679 }else{ 680 $name = $this->_xmlEntities($name); 681 } 682 } 683 684 $link['url'] = 'mailto:'.rawurlencode($address); 685 $link['name'] = $name; 686 $link['title'] = $title; 687 688 //output formatted 689 $this->doc .= $this->_formatLink($link); 690 } 691 692 /** 693 * @todo don't add link for flash 694 */ 695 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 696 $height=NULL, $cache=NULL) { 697 global $conf; 698 global $ID; 699 resolve_mediaid(getNS($ID),$src, $exists); 700 701 $link = array(); 702 $link['class'] = 'media'; 703 $link['style'] = ''; 704 $link['pre'] = ''; 705 $link['suf'] = ''; 706 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 707 $link['target'] = $conf['target']['media']; 708 709 $link['title'] = $this->_xmlEntities($src); 710 list($ext,$mime) = mimetype($src); 711 if(substr($mime,0,5) == 'image'){ 712 $link['url']= DOKU_BASE.'lib/exe/detail.php?id='.$ID.'&cache='.$cache.'&media='.urlencode($src); 713 }else{ 714 $link['url']= DOKU_BASE.'lib/exe/fetch.php?cache='.$cache.'&media='.urlencode($src); 715 } 716 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 717 718 719 //output formatted 720 $this->doc .= $this->_formatLink($link); 721 } 722 723 /** 724 * @todo don't add link for flash 725 */ 726 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 727 $height=NULL, $cache=NULL) { 728 global $conf; 729 730 $link = array(); 731 $link['class'] = 'media'; 732 $link['style'] = ''; 733 $link['pre'] = ''; 734 $link['suf'] = ''; 735 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 736 $link['target'] = $conf['target']['media']; 737 738 $link['title'] = $this->_xmlEntities($src); 739 $link['url'] = DOKU_BASE.'lib/exe/fetch.php?cache='.$cache.'&media='.urlencode($src); 740 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 741 742 743 //output formatted 744 $this->doc .= $this->_formatLink($link); 745 } 746 747 /** 748 * Renders an RSS feed using Magpie 749 * 750 * @author Andreas Gohr <andi@splitbrain.org> 751 */ 752 function rss ($url){ 753 global $lang; 754 define('MAGPIE_CACHE_ON', false); //we do our own caching 755 define('MAGPIE_DIR', DOKU_INC.'inc/magpie/'); 756 define('MAGPIE_OUTPUT_ENCODING','UTF-8'); //return all feeds as UTF-8 757 require_once(MAGPIE_DIR.'/rss_fetch.inc'); 758 759 //disable warning while fetching 760 $elvl = error_reporting(E_ERROR); 761 $rss = fetch_rss($url); 762 error_reporting($elvl); 763 764 $this->doc .= '<ul class="rss">'; 765 if($rss){ 766 foreach ($rss->items as $item ) { 767 $this->doc .= '<li>'; 768 $this->externallink($item['link'],$item['title']); 769 $this->doc .= '</li>'; 770 } 771 }else{ 772 $this->doc .= '<li>'; 773 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 774 $this->externallink($url); 775 $this->doc .= '</li>'; 776 } 777 $this->doc .= '</ul>'; 778 } 779 780 // $numrows not yet implemented 781 function table_open($maxcols = NULL, $numrows = NULL){ 782 $this->doc .= '<table class="inline">'.DOKU_LF; 783 } 784 785 function table_close(){ 786 $this->doc .= '</table>'.DOKU_LF.'<br />'.DOKU_LF; 787 } 788 789 function tablerow_open(){ 790 $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB; 791 } 792 793 function tablerow_close(){ 794 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 795 } 796 797 function tableheader_open($colspan = 1, $align = NULL){ 798 $this->doc .= '<th'; 799 if ( !is_null($align) ) { 800 $this->doc .= ' class="'.$align.'align"'; 801 } 802 if ( $colspan > 1 ) { 803 $this->doc .= ' colspan="'.$colspan.'"'; 804 } 805 $this->doc .= '>'; 806 } 807 808 function tableheader_close(){ 809 $this->doc .= '</th>'; 810 } 811 812 function tablecell_open($colspan = 1, $align = NULL){ 813 $this->doc .= '<td'; 814 if ( !is_null($align) ) { 815 $this->doc .= ' class="'.$align.'align"'; 816 } 817 if ( $colspan > 1 ) { 818 $this->doc .= ' colspan="'.$colspan.'"'; 819 } 820 $this->doc .= '>'; 821 } 822 823 function tablecell_close(){ 824 $this->doc .= '</td>'; 825 } 826 827 //---------------------------------------------------------- 828 // Utils 829 830 /** 831 * Build a link 832 * 833 * Assembles all parts defined in $link returns HTML for the link 834 * 835 * @author Andreas Gohr <andi@splitbrain.org> 836 */ 837 function _formatLink($link){ 838 //make sure the url is XHTML compliant (skip mailto) 839 if(substr($link['url'],0,7) != 'mailto:'){ 840 $link['url'] = str_replace('&','&',$link['url']); 841 $link['url'] = str_replace('&amp;','&',$link['url']); 842 } 843 //remove double encodings in titles 844 $link['title'] = str_replace('&amp;','&',$link['title']); 845 846 $ret = ''; 847 $ret .= $link['pre']; 848 $ret .= '<a href="'.$link['url'].'"'; 849 if($link['class']) $ret .= ' class="'.$link['class'].'"'; 850 if($link['target']) $ret .= ' target="'.$link['target'].'"'; 851 if($link['title']) $ret .= ' title="'.$link['title'].'"'; 852 if($link['style']) $ret .= ' style="'.$link['style'].'"'; 853 if($link['more']) $ret .= ' '.$link['more']; 854 $ret .= '>'; 855 $ret .= $link['name']; 856 $ret .= '</a>'; 857 $ret .= $link['suf']; 858 return $ret; 859 } 860 861 /** 862 * Removes any Namespace from the given name but keeps 863 * casing and special chars 864 * 865 * @author Andreas Gohr <andi@splitbrain.org> 866 */ 867 function _simpleTitle($name){ 868 global $conf; 869 870 if($conf['useslash']){ 871 $nssep = '[:;/]'; 872 }else{ 873 $nssep = '[:;]'; 874 } 875 $name = preg_replace('!.*'.$nssep.'!','',$name); 876 //if there is a hash we use the ancor name only 877 $name = preg_replace('!.*#!','',$name); 878 return $name; 879 } 880 881 /** 882 * Renders internal and external media 883 * 884 * @author Andreas Gohr <andi@splitbrain.org> 885 */ 886 function _media ($src, $title=NULL, $align=NULL, $width=NULL, 887 $height=NULL, $cache=NULL) { 888 889 $ret = ''; 890 891 list($ext,$mime) = mimetype($src); 892 if(substr($mime,0,5) == 'image'){ 893 //add image tag 894 $ret .= '<img src="'.DOKU_BASE.'lib/exe/fetch.php?w='.$width.'&h='.$height. 895 '&cache='.$cache.'&media='.urlencode($src).'"'; 896 897 $ret .= ' class="media'.$align.'"'; 898 899 if (!is_null($title)) { 900 $ret .= ' title="'.$this->_xmlEntities($title).'"'; 901 $ret .= ' alt="'.$this->_xmlEntities($title).'"'; 902 }elseif($ext == 'jpg' || $ext == 'jpeg'){ 903 //try to use the caption from IPTC/EXIF 904 require_once(DOKU_INC.'inc/JpegMeta.php'); 905 $jpeg =& new JpegMeta(mediaFN($src)); 906 if($jpeg !== false) $cap = $jpeg->getTitle(); 907 if($cap){ 908 $ret .= ' title="'.$this->_xmlEntities($cap).'"'; 909 $ret .= ' alt="'.$this->_xmlEntities($cap).'"'; 910 } 911 }else{ 912 $ret .= ' alt=""'; 913 } 914 915 if ( !is_null($width) ) 916 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 917 918 if ( !is_null($height) ) 919 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 920 921 $ret .= ' />'; 922 923 }elseif($mime == 'application/x-shockwave-flash'){ 924 $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'. 925 ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"'; 926 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 927 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 928 $ret .= '>'.DOKU_LF; 929 $ret .= '<param name="movie" value="'.DOKU_BASE.'lib/exe/fetch.php?media='.urlencode($src).'" />'.DOKU_LF; 930 $ret .= '<param name="quality" value="high" />'.DOKU_LF; 931 $ret .= '<embed src="'.DOKU_BASE.'lib/exe/fetch.php?media='.urlencode($src).'"'. 932 ' quality="high"'; 933 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 934 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 935 $ret .= ' type="application/x-shockwave-flash"'. 936 ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF; 937 $ret .= '</object>'.DOKU_LF; 938 939 }elseif(!is_null($title)){ 940 // well at least we have a title to display 941 $ret .= $this->_xmlEntities($title); 942 }else{ 943 // just show the source 944 $ret .= $this->_xmlEntities($src); 945 } 946 947 return $ret; 948 } 949 950 function _xmlEntities($string) { 951 return htmlspecialchars($string); 952 } 953 954 function _headerToLink($title) { 955 return str_replace(':','',cleanID($title)); 956 } 957 958 /** 959 * Adds code for section editing button 960 * 961 * This is just aplaceholder and gets replace by the button if 962 * section editing is allowed 963 * 964 * @author Andreas Gohr <andi@splitbrain.org> 965 */ 966 function _secedit($f, $t){ 967 $this->doc .= '<!-- SECTION ['.$f.'-'.$t.'] -->'; 968 } 969 970 /** 971 * Construct a title and handle images in titles 972 * 973 * @author Harry Fuecks <hfuecks@gmail.com> 974 */ 975 function _getLinkTitle($title, $default, & $isImage, $id=NULL) { 976 global $conf; 977 978 $isImage = FALSE; 979 if ( is_null($title) ) { 980 if ($conf['useheading'] && $id) { 981 $heading = p_get_first_heading($id); 982 if ($heading) { 983 return $this->_xmlEntities($heading); 984 } 985 } 986 return $this->_xmlEntities($default); 987 } else if ( is_string($title) ) { 988 return $this->_xmlEntities($title); 989 } else if ( is_array($title) ) { 990 $isImage = TRUE; 991 return $this->_imageTitle($title); 992 } 993 } 994 995 /** 996 * Returns an HTML code for images used in link titles 997 * 998 * @todo Resolve namespace on internal images 999 * @author Andreas Gohr <andi@splitbrain.org> 1000 */ 1001 function _imageTitle($img) { 1002 return $this->_media($img['src'], 1003 $img['title'], 1004 $img['align'], 1005 $img['width'], 1006 $img['height'], 1007 $img['cache']); 1008 } 1009} 1010 1011//Setup VIM: ex: et ts=4 enc=utf-8 : 1012