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