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