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 .= $lang['toc']; 74 $this->doc .= ' <script type="text/javascript">showTocToggle("+","-")'; 75 $this->doc .= '</script></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 .= '<span class="li">'; 274 } 275 276 function listcontent_close() { 277 $this->doc .= '</span>'.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'); 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 /** 375 */ 376 function smiley($smiley) { 377 if ( array_key_exists($smiley, $this->smileys) ) { 378 $title = $this->_xmlEntities($this->smileys[$smiley]); 379 $this->doc .= '<img src="'.DOKU_BASE.'smileys/'.$this->smileys[$smiley]. 380 '" align="middle" alt="'. 381 $this->_xmlEntities($smiley).'" />'; 382 } else { 383 $this->doc .= $this->_xmlEntities($smiley); 384 } 385 } 386 387 /** 388 * not used 389 function wordblock($word) { 390 if ( array_key_exists($word, $this->badwords) ) { 391 $this->doc .= '** BLEEP **'; 392 } else { 393 $this->doc .= $this->_xmlEntities($word); 394 } 395 } 396 */ 397 398 function entity($entity) { 399 if ( array_key_exists($entity, $this->entities) ) { 400 $this->doc .= $this->entities[$entity]; 401 } else { 402 $this->doc .= $this->_xmlEntities($entity); 403 } 404 } 405 406 function multiplyentity($x, $y) { 407 $this->doc .= "$x×$y"; 408 } 409 410 function singlequoteopening() { 411 $this->doc .= "‘"; 412 } 413 414 function singlequoteclosing() { 415 $this->doc .= "’"; 416 } 417 418 function doublequoteopening() { 419 $this->doc .= "“"; 420 } 421 422 function doublequoteclosing() { 423 $this->doc .= "”"; 424 } 425 426 /** 427 */ 428 function camelcaselink($link) { 429 $this->internallink($link,$link); 430 } 431 432 433 function locallink($hash, $name = NULL){ 434 global $ID; 435 $name = $this->_getLinkTitle($name, $hash, $isImage); 436 $hash = $this->_headerToLink($hash); 437 $title = $ID.' ↵'; 438 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 439 $this->doc .= $name; 440 $this->doc .= '</a>'; 441 } 442 443 /** 444 * Render an internal Wiki Link 445 * 446 * $search and $returnonly are not for the renderer but are used 447 * elsewhere - no need to implement them in other renderers 448 * 449 * @author Andreas Gohr <andi@splitbrain.org> 450 */ 451 function internallink($id, $name = NULL, $search=NULL,$returnonly=false) { 452 global $conf; 453 global $ID; 454 // default name is based on $id as given 455 $default = $this->_simpleTitle($id); 456 // now first resolve and clean up the $id 457 resolve_pageid(getNS($ID),$id,$exists); 458 $name = $this->_getLinkTitle($name, $default, $isImage, $id); 459 if ( !$isImage ) { 460 if ( $exists ) { 461 $class='wikilink1'; 462 } else { 463 $class='wikilink2'; 464 } 465 } else { 466 $class='media'; 467 } 468 469 //keep hash anchor 470 list($id,$hash) = split('#',$id,2); 471 472 //prepare for formating 473 $link['target'] = $conf['target']['wiki']; 474 $link['style'] = ''; 475 $link['pre'] = ''; 476 $link['suf'] = ''; 477 // highlight link to current page 478 if ($id == $ID) { 479 $link['pre'] = '<span class="curid">'; 480 $link['suf'] = '</span>'; 481 } 482 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 483 $link['class'] = $class; 484 $link['url'] = wl($id); 485 $link['name'] = $name; 486 $link['title'] = $id; 487 //add search string 488 if($search){ 489 ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&s='; 490 $link['url'] .= urlencode($search); 491 } 492 493 //keep hash 494 if($hash) $link['url'].='#'.$hash; 495 496 //output formatted 497 if($returnonly){ 498 return $this->_formatLink($link); 499 }else{ 500 $this->doc .= $this->_formatLink($link); 501 } 502 } 503 504 function externallink($url, $name = NULL) { 505 global $conf; 506 507 $name = $this->_getLinkTitle($name, $url, $isImage); 508 509 // add protocol on simple short URLs 510 if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')) $url = 'ftp://'.$url; 511 if(substr($url,0,3) == 'www') $url = 'http://'.$url; 512 513 if ( !$isImage ) { 514 $class='urlextern'; 515 } else { 516 $class='media'; 517 } 518 519 //prepare for formating 520 $link['target'] = $conf['target']['extern']; 521 $link['style'] = ''; 522 $link['pre'] = ''; 523 $link['suf'] = ''; 524 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 525 $link['class'] = $class; 526 $link['url'] = $url; 527 $link['name'] = $name; 528 $link['title'] = $this->_xmlEntities($url); 529 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 530 531 //output formatted 532 $this->doc .= $this->_formatLink($link); 533 } 534 535 /** 536 */ 537 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { 538 global $conf; 539 540 $link = array(); 541 $link['target'] = $conf['target']['interwiki']; 542 $link['pre'] = ''; 543 $link['suf'] = ''; 544 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 545 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 546 547 if ( !$isImage ) { 548 $link['class'] = 'interwiki'; 549 } else { 550 $link['class'] = 'media'; 551 } 552 553 //get interwiki URL 554 if ( isset($this->interwiki[$wikiName]) ) { 555 $url = $this->interwiki[$wikiName]; 556 } else { 557 // Default to Google I'm feeling lucky 558 $url = 'http://www.google.com/search?q={URL}&btnI=lucky'; 559 $wikiName = 'go'; 560 } 561 562 if(!$isImage){ 563 //if ico exists set additional style 564 if(@file_exists(DOKU_INC.'interwiki/'.$wikiName.'.png')){ 565 $link['style']='background-image: url('.DOKU_BASE.'interwiki/'.$wikiName.'.png)'; 566 }elseif(@file_exists(DOKU_INC.'interwiki/'.$wikiName.'.gif')){ 567 $link['style']='background-image: url('.DOKU_BASE.'interwiki/'.$wikiName.'.gif)'; 568 } 569 } 570 571 //do we stay at the same server? Use local target 572 if( strpos($url,DOKU_URL) === 0 ){ 573 $link['target'] = $conf['target']['wiki']; 574 } 575 576 //replace placeholder 577 if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#',$url)){ 578 //use placeholders 579 $url = str_replace('{URL}',urlencode($wikiUri),$url); 580 $url = str_replace('{NAME}',$wikiUri,$url); 581 $parsed = parse_url($wikiUri); 582 if(!$parsed['port']) $parsed['port'] = 80; 583 $url = str_replace('{SCHEME}',$parsed['scheme'],$url); 584 $url = str_replace('{HOST}',$parsed['host'],$url); 585 $url = str_replace('{PORT}',$parsed['port'],$url); 586 $url = str_replace('{PATH}',$parsed['path'],$url); 587 $url = str_replace('{QUERY}',$parsed['query'],$url); 588 $link['url'] = $url; 589 }else{ 590 //default 591 $link['url'] = $url.urlencode($wikiUri); 592 } 593 594 $link['title'] = htmlspecialchars($link['url']); 595 596 //output formatted 597 $this->doc .= $this->_formatLink($link); 598 } 599 600 /** 601 */ 602 function windowssharelink($url, $name = NULL) { 603 global $conf; 604 global $lang; 605 //simple setup 606 $link['target'] = $conf['target']['windows']; 607 $link['pre'] = ''; 608 $link['suf'] = ''; 609 $link['style'] = ''; 610 //Display error on browsers other than IE 611 $link['more'] = 'onclick="if(document.all == null){alert(\''. 612 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES). 613 '\');}" onkeypress="if(document.all == null){alert(\''. 614 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"'; 615 616 $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 617 if ( !$isImage ) { 618 $link['class'] = 'windows'; 619 } else { 620 $link['class'] = 'media'; 621 } 622 623 624 $link['title'] = $this->_xmlEntities($url); 625 $url = str_replace('\\','/',$url); 626 $url = 'file:///'.$url; 627 $link['url'] = $url; 628 629 //output formatted 630 $this->doc .= $this->_formatLink($link); 631 } 632 633 function emaillink($address, $name = NULL) { 634 global $conf; 635 //simple setup 636 $link = array(); 637 $link['target'] = ''; 638 $link['pre'] = ''; 639 $link['suf'] = ''; 640 $link['style'] = ''; 641 $link['more'] = ''; 642 643 //we just test for image here - we need to encode the title our self 644 $this->_getLinkTitle($name, $address, $isImage); 645 if ( !$isImage ) { 646 $link['class']='mail'; 647 } else { 648 $link['class']='media'; 649 } 650 651 //shields up 652 if($conf['mailguard']=='visible'){ 653 //the mail name gets some visible encoding 654 $address = str_replace('@',' [at] ',$address); 655 $address = str_replace('.',' [dot] ',$address); 656 $address = str_replace('-',' [dash] ',$address); 657 658 $title = $this->_xmlEntities($address); 659 if(empty($name)){ 660 $name = $this->_xmlEntities($address); 661 }else{ 662 $name = $this->_xmlEntities($name); 663 } 664 }elseif($conf['mailguard']=='hex'){ 665 //encode every char to a hex entity 666 for ($x=0; $x < strlen($address); $x++) { 667 $encode .= '&#x' . bin2hex($address[$x]).';'; 668 } 669 $address = $encode; 670 $title = $encode; 671 if(empty($name)){ 672 $name = $encode; 673 }else{ 674 $name = $this->_xmlEntities($name); 675 } 676 }else{ 677 //keep address as is 678 $title = $this->_xmlEntities($address); 679 if(empty($name)){ 680 $name = $this->_xmlEntities($address); 681 }else{ 682 $name = $this->_xmlEntities($name); 683 } 684 } 685 686 $link['url'] = 'mailto:'.$address; 687 $link['name'] = $name; 688 $link['title'] = $title; 689 690 //output formatted 691 $this->doc .= $this->_formatLink($link); 692 } 693 694 /** 695 * @todo don't add link for flash 696 */ 697 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 698 $height=NULL, $cache=NULL) { 699 global $conf; 700 global $ID; 701 resolve_mediaid(getNS($ID),$src, $exists); 702 703 $link = array(); 704 $link['class'] = 'media'; 705 $link['style'] = ''; 706 $link['pre'] = ''; 707 $link['suf'] = ''; 708 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 709 $link['target'] = $conf['target']['media']; 710 711 $link['title'] = $this->_xmlEntities($src); 712 $link['url'] = DOKU_BASE.'fetch.php?cache='.$cache.'&media='.urlencode($src); 713 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 714 715 716 //output formatted 717 $this->doc .= $this->_formatLink($link); 718 } 719 720 /** 721 * @todo don't add link for flash 722 */ 723 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 724 $height=NULL, $cache=NULL) { 725 global $conf; 726 727 $link = array(); 728 $link['class'] = 'media'; 729 $link['style'] = ''; 730 $link['pre'] = ''; 731 $link['suf'] = ''; 732 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 733 $link['target'] = $conf['target']['media']; 734 735 $link['title'] = $this->_xmlEntities($src); 736 $link['url'] = DOKU_BASE.'fetch.php?cache='.$cache.'&media='.urlencode($src); 737 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 738 739 740 //output formatted 741 $this->doc .= $this->_formatLink($link); 742 } 743 744 /** 745 * Renders an RSS feed using Magpie 746 * 747 * @author Andreas Gohr <andi@splitbrain.org> 748 */ 749 function rss ($url){ 750 global $lang; 751 define('MAGPIE_CACHE_ON', false); //we do our own caching 752 define('MAGPIE_DIR', DOKU_INC.'inc/magpie/'); 753 define('MAGPIE_OUTPUT_ENCODING','UTF-8'); //return all feeds as UTF-8 754 require_once(MAGPIE_DIR.'/rss_fetch.inc'); 755 756 //disable warning while fetching 757 $elvl = error_reporting(E_ERROR); 758 $rss = fetch_rss($url); 759 error_reporting($elvl); 760 761 $this->doc .= '<ul class="rss">'; 762 if($rss){ 763 foreach ($rss->items as $item ) { 764 $this->doc .= '<li>'; 765 $this->externallink($item['link'],$item['title']); 766 $this->doc .= '</li>'; 767 } 768 }else{ 769 $this->doc .= '<li>'; 770 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 771 $this->externallink($url); 772 $this->doc .= '</li>'; 773 } 774 $this->doc .= '</ul>'; 775 } 776 777 // $numrows not yet implemented 778 function table_open($maxcols = NULL, $numrows = NULL){ 779 $this->doc .= '<table class="inline">'.DOKU_LF; 780 } 781 782 function table_close(){ 783 $this->doc .= '</table>'.DOKU_LF.'<br />'.DOKU_LF; 784 } 785 786 function tablerow_open(){ 787 $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB; 788 } 789 790 function tablerow_close(){ 791 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 792 } 793 794 function tableheader_open($colspan = 1, $align = NULL){ 795 $this->doc .= '<th'; 796 if ( !is_null($align) ) { 797 $this->doc .= ' class="'.$align.'align"'; 798 } 799 if ( $colspan > 1 ) { 800 $this->doc .= ' colspan="'.$colspan.'"'; 801 } 802 $this->doc .= '>'; 803 } 804 805 function tableheader_close(){ 806 $this->doc .= '</th>'; 807 } 808 809 function tablecell_open($colspan = 1, $align = NULL){ 810 $this->doc .= '<td'; 811 if ( !is_null($align) ) { 812 $this->doc .= ' class="'.$align.'align"'; 813 } 814 if ( $colspan > 1 ) { 815 $this->doc .= ' colspan="'.$colspan.'"'; 816 } 817 $this->doc .= '>'; 818 } 819 820 function tablecell_close(){ 821 $this->doc .= '</td>'; 822 } 823 824 //---------------------------------------------------------- 825 // Utils 826 827 /** 828 * Build a link 829 * 830 * Assembles all parts defined in $link returns HTML for the link 831 * 832 * @author Andreas Gohr <andi@splitbrain.org> 833 */ 834 function _formatLink($link){ 835 //make sure the url is XHTML compliant (skip mailto) 836 if(substr($link['url'],0,7) != 'mailto:'){ 837 $link['url'] = str_replace('&','&',$link['url']); 838 $link['url'] = str_replace('&amp;','&',$link['url']); 839 } 840 //remove double encodings in titles 841 $link['title'] = str_replace('&amp;','&',$link['title']); 842 843 $ret = ''; 844 $ret .= $link['pre']; 845 $ret .= '<a href="'.$link['url'].'"'; 846 if($link['class']) $ret .= ' class="'.$link['class'].'"'; 847 if($link['target']) $ret .= ' target="'.$link['target'].'"'; 848 if($link['title']) $ret .= ' title="'.$link['title'].'"'; 849 if($link['style']) $ret .= ' style="'.$link['style'].'"'; 850 if($link['more']) $ret .= ' '.$link['more']; 851 $ret .= '>'; 852 $ret .= $link['name']; 853 $ret .= '</a>'; 854 $ret .= $link['suf']; 855 return $ret; 856 } 857 858 /** 859 * Removes any Namespace from the given name but keeps 860 * casing and special chars 861 * 862 * @author Andreas Gohr <andi@splitbrain.org> 863 */ 864 function _simpleTitle($name){ 865 global $conf; 866 867 if($conf['useslash']){ 868 $nssep = '[:;/]'; 869 }else{ 870 $nssep = '[:;]'; 871 } 872 $name = preg_replace('!.*'.$nssep.'!','',$name); 873 //if there is a hash we use the ancor name only 874 $name = preg_replace('!.*#!','',$name); 875 return $name; 876 } 877 878 /** 879 * Renders internal and external media 880 * 881 * @author Andreas Gohr <andi@splitbrain.org> 882 */ 883 function _media ($src, $title=NULL, $align=NULL, $width=NULL, 884 $height=NULL, $cache=NULL) { 885 886 $ret = ''; 887 888 list($ext,$mime) = mimetype($src); 889 if(substr($mime,0,5) == 'image'){ 890 //add image tag 891 $ret .= '<img src="'.DOKU_BASE.'fetch.php?w='.$width.'&h='.$height. 892 '&cache='.$cache.'&media='.urlencode($src).'"'; 893 894 $ret .= ' class="media'.$align.'"'; 895 896 if (!is_null($title)) { 897 $ret .= ' title="'.$this->_xmlEntities($title).'"'; 898 $ret .= ' alt="'.$this->_xmlEntities($title).'"'; 899 }else{ 900 $ret .= ' alt=""'; 901 } 902 903 if ( !is_null($width) ) 904 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 905 906 if ( !is_null($height) ) 907 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 908 909 $ret .= ' />'; 910 911 }elseif($mime == 'application/x-shockwave-flash'){ 912 $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'. 913 ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"'; 914 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 915 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 916 $ret .= '>'.DOKU_LF; 917 $ret .= '<param name="movie" value="'.DOKU_BASE.'fetch.php?media='.urlencode($src).'" />'.DOKU_LF; 918 $ret .= '<param name="quality" value="high" />'.DOKU_LF; 919 $ret .= '<embed src="'.DOKU_BASE.'fetch.php?media='.urlencode($src).'"'. 920 ' quality="high"'; 921 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 922 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 923 $ret .= ' type="application/x-shockwave-flash"'. 924 ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF; 925 $ret .= '</object>'.DOKU_LF; 926 927 }elseif(!is_null($title)){ 928 // well at least we have a title to display 929 $ret .= $this->_xmlEntities($title); 930 }else{ 931 // just show the source 932 $ret .= $this->_xmlEntities($src); 933 } 934 935 return $ret; 936 } 937 938 function _xmlEntities($string) { 939 return htmlspecialchars($string); 940 } 941 942 function _headerToLink($title) { 943 return str_replace(':','',cleanID($title)); 944 } 945 946 /** 947 * Adds code for section editing button 948 * 949 * This is just aplaceholder and gets replace by the button if 950 * section editing is allowed 951 * 952 * @author Andreas Gohr <andi@splitbrain.org> 953 */ 954 function _secedit($f, $t){ 955 $this->doc .= '<!-- SECTION ['.$f.'-'.$t.'] -->'; 956 } 957 958 /** 959 * Construct a title and handle images in titles 960 * 961 * @author Harry Fuecks <hfuecks@gmail.com> 962 */ 963 function _getLinkTitle($title, $default, & $isImage, $id=NULL) { 964 global $conf; 965 966 $isImage = FALSE; 967 if ( is_null($title) ) { 968 if ($conf['useheading'] && $id) { 969 $heading = p_get_first_heading($id); 970 if ($heading) { 971 return $this->_xmlEntities($heading); 972 } 973 } 974 return $this->_xmlEntities($default); 975 } else if ( is_string($title) ) { 976 return $this->_xmlEntities($title); 977 } else if ( is_array($title) ) { 978 $isImage = TRUE; 979 return $this->_imageTitle($title); 980 } 981 } 982 983 /** 984 * Returns an HTML code for images used in link titles 985 * 986 * @todo Resolve namespace on internal images 987 * @author Andreas Gohr <andi@splitbrain.org> 988 */ 989 function _imageTitle($img) { 990 return $this->_media($img['src'], 991 $img['title'], 992 $img['align'], 993 $img['width'], 994 $img['height'], 995 $img['cache']); 996 } 997} 998 999//Setup VIM: ex: et ts=4 enc=utf-8 : 1000