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