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