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 global $lang; 445 $this->doc .= $lang['singlequoteopening']; 446 } 447 448 function singlequoteclosing() { 449 global $lang; 450 $this->doc .= $lang['singlequoteclosing']; 451 } 452 453 function doublequoteopening() { 454 global $lang; 455 $this->doc .= $lang['doublequoteopening']; 456 } 457 458 function doublequoteclosing() { 459 global $lang; 460 $this->doc .= $lang['doublequoteclosing']; 461 } 462 463 /** 464 */ 465 function camelcaselink($link) { 466 $this->internallink($link,$link); 467 } 468 469 470 function locallink($hash, $name = NULL){ 471 global $ID; 472 $name = $this->_getLinkTitle($name, $hash, $isImage); 473 $hash = $this->_headerToLink($hash); 474 $title = $ID.' ↵'; 475 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 476 $this->doc .= $name; 477 $this->doc .= '</a>'; 478 } 479 480 /** 481 * Render an internal Wiki Link 482 * 483 * $search and $returnonly are not for the renderer but are used 484 * elsewhere - no need to implement them in other renderers 485 * 486 * @author Andreas Gohr <andi@splitbrain.org> 487 */ 488 function internallink($id, $name = NULL, $search=NULL,$returnonly=false) { 489 global $conf; 490 global $ID; 491 // default name is based on $id as given 492 $default = $this->_simpleTitle($id); 493 // now first resolve and clean up the $id 494 resolve_pageid(getNS($ID),$id,$exists); 495 $name = $this->_getLinkTitle($name, $default, $isImage, $id); 496 if ( !$isImage ) { 497 if ( $exists ) { 498 $class='wikilink1'; 499 } else { 500 $class='wikilink2'; 501 } 502 } else { 503 $class='media'; 504 } 505 506 //keep hash anchor 507 list($id,$hash) = explode('#',$id,2); 508 509 //prepare for formating 510 $link['target'] = $conf['target']['wiki']; 511 $link['style'] = ''; 512 $link['pre'] = ''; 513 $link['suf'] = ''; 514 // highlight link to current page 515 if ($id == $ID) { 516 $link['pre'] = '<span class="curid">'; 517 $link['suf'] = '</span>'; 518 } 519 $link['more'] = ''; 520 $link['class'] = $class; 521 $link['url'] = wl($id); 522 $link['name'] = $name; 523 $link['title'] = $id; 524 //add search string 525 if($search){ 526 ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&s='; 527 $link['url'] .= rawurlencode($search); 528 } 529 530 //keep hash 531 if($hash) $link['url'].='#'.$hash; 532 533 //output formatted 534 if($returnonly){ 535 return $this->_formatLink($link); 536 }else{ 537 $this->doc .= $this->_formatLink($link); 538 } 539 } 540 541 function externallink($url, $name = NULL) { 542 global $conf; 543 544 $name = $this->_getLinkTitle($name, $url, $isImage); 545 546 if ( !$isImage ) { 547 $class='urlextern'; 548 } else { 549 $class='media'; 550 } 551 552 //prepare for formating 553 $link['target'] = $conf['target']['extern']; 554 $link['style'] = ''; 555 $link['pre'] = ''; 556 $link['suf'] = ''; 557 $link['more'] = ''; 558 $link['class'] = $class; 559 $link['url'] = $url; 560 561 $link['name'] = $name; 562 $link['title'] = $this->_xmlEntities($url); 563 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 564 565 //output formatted 566 $this->doc .= $this->_formatLink($link); 567 } 568 569 /** 570 */ 571 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { 572 global $conf; 573 574 $link = array(); 575 $link['target'] = $conf['target']['interwiki']; 576 $link['pre'] = ''; 577 $link['suf'] = ''; 578 $link['more'] = ''; 579 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 580 581 //get interwiki URL 582 $url = $this-> _resolveInterWiki($wikiName,$wikiUri); 583 584 if ( !$isImage ) { 585 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName); 586 $link['class'] = "interwiki iw_$class"; 587 } else { 588 $link['class'] = 'media'; 589 } 590 591 //do we stay at the same server? Use local target 592 if( strpos($url,DOKU_URL) === 0 ){ 593 $link['target'] = $conf['target']['wiki']; 594 } 595 596 $link['url'] = $url; 597 $link['title'] = htmlspecialchars($link['url']); 598 599 //output formatted 600 $this->doc .= $this->_formatLink($link); 601 } 602 603 /** 604 */ 605 function windowssharelink($url, $name = NULL) { 606 global $conf; 607 global $lang; 608 //simple setup 609 $link['target'] = $conf['target']['windows']; 610 $link['pre'] = ''; 611 $link['suf'] = ''; 612 $link['style'] = ''; 613 //Display error on browsers other than IE 614 $link['more'] = 'onclick="if(document.all == null){alert(\''. 615 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES). 616 '\');}" onkeypress="if(document.all == null){alert(\''. 617 $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"'; 618 619 $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 620 if ( !$isImage ) { 621 $link['class'] = 'windows'; 622 } else { 623 $link['class'] = 'media'; 624 } 625 626 627 $link['title'] = $this->_xmlEntities($url); 628 $url = str_replace('\\','/',$url); 629 $url = 'file:///'.$url; 630 $link['url'] = $url; 631 632 //output formatted 633 $this->doc .= $this->_formatLink($link); 634 } 635 636 function emaillink($address, $name = NULL) { 637 global $conf; 638 //simple setup 639 $link = array(); 640 $link['target'] = ''; 641 $link['pre'] = ''; 642 $link['suf'] = ''; 643 $link['style'] = ''; 644 $link['more'] = ''; 645 646 $name = $this->_getLinkTitle($name, '', $isImage); 647 if ( !$isImage ) { 648 $link['class']='mail JSnocheck'; 649 } else { 650 $link['class']='media JSnocheck'; 651 } 652 653 $address = $this->_xmlEntities($address); 654 $address = obfuscate($address); 655 $title = $address; 656 657 if(empty($name)){ 658 $name = $address; 659 } 660#elseif($isImage{ 661# $name = $this->_xmlEntities($name); 662# } 663 664 if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 665 666 $link['url'] = 'mailto:'.$address; 667 $link['name'] = $name; 668 $link['title'] = $title; 669 670 //output formatted 671 $this->doc .= $this->_formatLink($link); 672 } 673 674 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 675 $height=NULL, $cache=NULL, $linking=NULL) { 676 global $conf; 677 global $ID; 678 resolve_mediaid(getNS($ID),$src, $exists); 679 680 $link = array(); 681 $link['class'] = 'media'; 682 $link['style'] = ''; 683 $link['pre'] = ''; 684 $link['suf'] = ''; 685 $link['more'] = ''; 686 $link['target'] = $conf['target']['media']; 687 $noLink = false; 688 689 $link['title'] = $this->_xmlEntities($src); 690 list($ext,$mime) = mimetype($src); 691 if(substr($mime,0,5) == 'image'){ 692 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct')); 693 }elseif($mime == 'application/x-shockwave-flash'){ 694 // don't link flash movies 695 $noLink = true; 696 }else{ 697 // add file icons 698 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 699 $link['class'] .= ' mediafile mf_'.$class; 700 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true); 701 } 702 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 703 704 //output formatted 705 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 706 else $this->doc .= $this->_formatLink($link); 707 } 708 709 /** 710 * @todo don't add link for flash 711 */ 712 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 713 $height=NULL, $cache=NULL, $linking=NULL) { 714 global $conf; 715 716 $link = array(); 717 $link['class'] = 'media'; 718 $link['style'] = ''; 719 $link['pre'] = ''; 720 $link['suf'] = ''; 721 $link['more'] = ''; 722 $link['target'] = $conf['target']['media']; 723 724 $link['title'] = $this->_xmlEntities($src); 725 $link['url'] = ml($src,array('cache'=>$cache)); 726 $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache); 727 $noLink = false; 728 729 list($ext,$mime) = mimetype($src); 730 if(substr($mime,0,5) == 'image'){ 731 // link only jpeg images 732 // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 733 }elseif($mime == 'application/x-shockwave-flash'){ 734 // don't link flash movies 735 $noLink = true; 736 }else{ 737 // add file icons 738 $link['class'] .= ' mediafile mf_'.$ext; 739 } 740 741 //output formatted 742 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 743 else $this->doc .= $this->_formatLink($link); 744 } 745 746 /** 747 * Renders an RSS feed 748 * 749 * @author Andreas Gohr <andi@splitbrain.org> 750 */ 751 function rss ($url,$params){ 752 global $lang; 753 global $conf; 754 755 require_once(DOKU_INC.'inc/FeedParser.php'); 756 $feed = new FeedParser(); 757 $feed->feed_url($url); 758 759 //disable warning while fetching 760 if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); } 761 $rc = $feed->init(); 762 if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); } 763 764 //decide on start and end 765 if($params['reverse']){ 766 $mod = -1; 767 $start = $feed->get_item_quantity()-1; 768 $end = $start - ($params['max']); 769 $end = ($end < -1) ? -1 : $end; 770 }else{ 771 $mod = 1; 772 $start = 0; 773 $end = $feed->get_item_quantity(); 774 $end = ($end > $params['max']) ? $params['max'] : $end;; 775 } 776 777 $this->doc .= '<ul class="rss">'; 778 if($rc){ 779 for ($x = $start; $x != $end; $x += $mod) { 780 $item = $feed->get_item($x); 781 $this->doc .= '<li><div class="li">'; 782 $this->externallink($item->get_permalink(), 783 $item->get_title()); 784 if($params['author']){ 785 $author = $item->get_author(0); 786 if($author){ 787 $name = $author->get_name(); 788 if(!$name) $name = $author->get_email(); 789 if($name) $this->doc .= ' '.$lang['by'].' '.$name; 790 } 791 } 792 if($params['date']){ 793 $this->doc .= ' ('.$item->get_date($conf['dformat']).')'; 794 } 795 if($params['details']){ 796 $this->doc .= '<div class="detail">'; 797 if($htmlok){ 798 $this->doc .= $item->get_description(); 799 }else{ 800 $this->doc .= strip_tags($item->get_description()); 801 } 802 $this->doc .= '</div>'; 803 } 804 805 $this->doc .= '</div></li>'; 806 } 807 }else{ 808 $this->doc .= '<li><div class="li">'; 809 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 810 $this->externallink($url); 811 $this->doc .= '</div></li>'; 812 } 813 $this->doc .= '</ul>'; 814 } 815 816 // $numrows not yet implemented 817 function table_open($maxcols = NULL, $numrows = NULL){ 818 $this->doc .= '<table class="inline">'.DOKU_LF; 819 } 820 821 function table_close(){ 822 $this->doc .= '</table>'.DOKU_LF; 823 } 824 825 function tablerow_open(){ 826 $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB; 827 } 828 829 function tablerow_close(){ 830 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 831 } 832 833 function tableheader_open($colspan = 1, $align = NULL){ 834 $this->doc .= '<th'; 835 if ( !is_null($align) ) { 836 $this->doc .= ' class="'.$align.'align"'; 837 } 838 if ( $colspan > 1 ) { 839 $this->doc .= ' colspan="'.$colspan.'"'; 840 } 841 $this->doc .= '>'; 842 } 843 844 function tableheader_close(){ 845 $this->doc .= '</th>'; 846 } 847 848 function tablecell_open($colspan = 1, $align = NULL){ 849 $this->doc .= '<td'; 850 if ( !is_null($align) ) { 851 $this->doc .= ' class="'.$align.'align"'; 852 } 853 if ( $colspan > 1 ) { 854 $this->doc .= ' colspan="'.$colspan.'"'; 855 } 856 $this->doc .= '>'; 857 } 858 859 function tablecell_close(){ 860 $this->doc .= '</td>'; 861 } 862 863 //---------------------------------------------------------- 864 // Utils 865 866 /** 867 * Build a link 868 * 869 * Assembles all parts defined in $link returns HTML for the link 870 * 871 * @author Andreas Gohr <andi@splitbrain.org> 872 */ 873 function _formatLink($link){ 874 //make sure the url is XHTML compliant (skip mailto) 875 if(substr($link['url'],0,7) != 'mailto:'){ 876 $link['url'] = str_replace('&','&',$link['url']); 877 $link['url'] = str_replace('&amp;','&',$link['url']); 878 } 879 //remove double encodings in titles 880 $link['title'] = str_replace('&amp;','&',$link['title']); 881 882 // be sure there are no bad chars in url or title 883 // (we can't do this for name because it can contain an img tag) 884 $link['url'] = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22')); 885 $link['title'] = strtr($link['title'],array('>'=>'>','<'=>'<','"'=>'"')); 886 887 $ret = ''; 888 $ret .= $link['pre']; 889 $ret .= '<a href="'.$link['url'].'"'; 890 if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 891 if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 892 if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 893 if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 894 if(!empty($link['more'])) $ret .= ' '.$link['more']; 895 $ret .= '>'; 896 $ret .= $link['name']; 897 $ret .= '</a>'; 898 $ret .= $link['suf']; 899 return $ret; 900 } 901 902 /** 903 * Renders internal and external media 904 * 905 * @author Andreas Gohr <andi@splitbrain.org> 906 */ 907 function _media ($src, $title=NULL, $align=NULL, $width=NULL, 908 $height=NULL, $cache=NULL) { 909 910 $ret = ''; 911 912 list($ext,$mime) = mimetype($src); 913 if(substr($mime,0,5) == 'image'){ 914 //add image tag 915 $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"'; 916 $ret .= ' class="media'.$align.'"'; 917 918 if (!is_null($title)) { 919 $ret .= ' title="'.$this->_xmlEntities($title).'"'; 920 $ret .= ' alt="'.$this->_xmlEntities($title).'"'; 921 }elseif($ext == 'jpg' || $ext == 'jpeg'){ 922 //try to use the caption from IPTC/EXIF 923 require_once(DOKU_INC.'inc/JpegMeta.php'); 924 $jpeg =& new JpegMeta(mediaFN($src)); 925 if($jpeg !== false) $cap = $jpeg->getTitle(); 926 if($cap){ 927 $ret .= ' title="'.$this->_xmlEntities($cap).'"'; 928 $ret .= ' alt="'.$this->_xmlEntities($cap).'"'; 929 } 930 }else{ 931 $ret .= ' alt=""'; 932 } 933 934 if ( !is_null($width) ) 935 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 936 937 if ( !is_null($height) ) 938 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 939 940 $ret .= ' />'; 941 942 }elseif($mime == 'application/x-shockwave-flash'){ 943 $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'. 944 ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"'; 945 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 946 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 947 $ret .= '>'.DOKU_LF; 948 $ret .= '<param name="movie" value="'.ml($src).'" />'.DOKU_LF; 949 $ret .= '<param name="quality" value="high" />'.DOKU_LF; 950 $ret .= '<embed src="'.ml($src).'"'. 951 ' quality="high"'; 952 if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 953 if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 954 $ret .= ' type="application/x-shockwave-flash"'. 955 ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF; 956 $ret .= '</object>'.DOKU_LF; 957 958 }elseif($title){ 959 // well at least we have a title to display 960 $ret .= $this->_xmlEntities($title); 961 }else{ 962 // just show the sourcename 963 $ret .= $this->_xmlEntities(basename(noNS($src))); 964 } 965 966 return $ret; 967 } 968 969 function _xmlEntities($string) { 970 return htmlspecialchars($string,ENT_QUOTES,'UTF-8'); 971 } 972 973 /** 974 * Creates a linkid from a headline 975 * 976 * @param string $title The headline title 977 * @param boolean $create Create a new unique ID? 978 * @author Andreas Gohr <andi@splitbrain.org> 979 */ 980 function _headerToLink($title,$create=false) { 981 $title = str_replace(':','',cleanID($title)); 982 $title = ltrim($title,'0123456789._-'); 983 if(empty($title)) $title='section'; 984 985 if($create){ 986 // make sure tiles are unique 987 $num = ''; 988 while(in_array($title.$num,$this->headers)){ 989 ($num) ? $num++ : $num = 1; 990 } 991 $title = $title.$num; 992 $this->headers[] = $title; 993 } 994 995 return $title; 996 } 997 998 /** 999 * Construct a title and handle images in titles 1000 * 1001 * @author Harry Fuecks <hfuecks@gmail.com> 1002 */ 1003 function _getLinkTitle($title, $default, & $isImage, $id=NULL) { 1004 global $conf; 1005 1006 $isImage = false; 1007 if ( is_null($title) ) { 1008 if ($conf['useheading'] && $id) { 1009 $heading = p_get_first_heading($id,true); 1010 if ($heading) { 1011 return $this->_xmlEntities($heading); 1012 } 1013 } 1014 return $this->_xmlEntities($default); 1015 } else if ( is_string($title) ) { 1016 return $this->_xmlEntities($title); 1017 } else if ( is_array($title) ) { 1018 $isImage = true; 1019 return $this->_imageTitle($title); 1020 } 1021 } 1022 1023 /** 1024 * Returns an HTML code for images used in link titles 1025 * 1026 * @todo Resolve namespace on internal images 1027 * @author Andreas Gohr <andi@splitbrain.org> 1028 */ 1029 function _imageTitle($img) { 1030 return $this->_media($img['src'], 1031 $img['title'], 1032 $img['align'], 1033 $img['width'], 1034 $img['height'], 1035 $img['cache']); 1036 } 1037} 1038 1039//Setup VIM: ex: et ts=4 enc=utf-8 : 1040