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