1<?php 2/** 3 * Renderer for XHTML output 4 * 5 * @author Harry Fuecks <hfuecks@gmail.com> 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8if(!defined('DOKU_INC')) die('meh.'); 9 10if ( !defined('DOKU_LF') ) { 11 // Some whitespace to help View > Source 12 define ('DOKU_LF',"\n"); 13} 14 15if ( !defined('DOKU_TAB') ) { 16 // Some whitespace to help View > Source 17 define ('DOKU_TAB',"\t"); 18} 19 20require_once DOKU_INC . 'inc/parser/renderer.php'; 21require_once DOKU_INC . 'inc/html.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 var $footnotes = array(); 35 var $pnid = 0; 36 var $lastlevel = 0; 37 var $node = array(0,0,0,0,0); 38 var $store = ''; 39 40 var $_counter = array(); // used as global counter, introduced for table classes 41 42 function getFormat(){ 43 return 'xhtml'; 44 } 45 46 47 function document_start() { 48 //reset some internals 49 $this->toc = array(); 50 $this->headers = array(); 51 } 52 53 function document_end() { 54 if ( count ($this->footnotes) > 0 ) { 55 $this->doc .= '<div class="footnotes">'.DOKU_LF; 56 57 $id = 0; 58 foreach ( $this->footnotes as $footnote ) { 59 $id++; // the number of the current footnote 60 61 // check its not a placeholder that indicates actual footnote text is elsewhere 62 if (substr($footnote, 0, 5) != "@@FNT") { 63 64 // open the footnote and set the anchor and backlink 65 $this->doc .= '<div class="fn">'; 66 $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" name="fn__'.$id.'" class="fn_bot">'; 67 $this->doc .= $id.')</a></sup> '.DOKU_LF; 68 69 // get any other footnotes that use the same markup 70 $alt = array_keys($this->footnotes, "@@FNT$id"); 71 72 if (count($alt)) { 73 foreach ($alt as $ref) { 74 // set anchor and backlink for the other footnotes 75 $this->doc .= ', <sup><a href="#fnt__'.($ref+1).'" id="fn__'.($ref+1).'" name="fn__'.($ref+1).'" class="fn_bot">'; 76 $this->doc .= ($ref+1).')</a></sup> '.DOKU_LF; 77 } 78 } 79 80 // add footnote markup and close this footnote 81 $this->doc .= $footnote; 82 $this->doc .= '</div>' . DOKU_LF; 83 } 84 } 85 $this->doc .= '</div>'.DOKU_LF; 86 } 87 88 // Prepare the TOC 89 global $conf; 90 if($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']){ 91 global $TOC; 92 $TOC = $this->toc; 93 } 94 95 // make sure there are no empty paragraphs 96 $this->doc = preg_replace('#<p>\s*</p>#','',$this->doc); 97 if ($conf['purplenumbers']) $this->doc = preg_replace('#<p[^>]*>\s*<!--PN-->.*?(?:</p>)#','',$this->doc); 98 } 99 100 function toc_additem($id, $text, $level) { 101 global $conf; 102 103 //handle TOC 104 if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){ 105 $this->toc[] = html_mktocitem($id, $text, $level-$conf['toptoclevel']+1); 106 } 107 } 108 109 function header($text, $level, $pos) { 110 global $conf; 111 global $lang; 112 if(!$text) return; //skip empty headlines 113 114 $hid = $this->_headerToLink($text,true); 115 116 //only add items within configured levels 117 $this->toc_additem($hid, $text, $level); 118 119 // adjust $node to reflect hierarchy of levels 120 $this->node[$level-1]++; 121 if ($level < $this->lastlevel) { 122 for ($i = 0; $i < $this->lastlevel-$level; $i++) { 123 $this->node[$this->lastlevel-$i-1] = 0; 124 } 125 } 126 $this->pnid = 0; 127 $this->lastlevel = $level; 128 129 // write the header 130 $this->doc .= DOKU_LF.'<h'.$level.'><a name="'.$hid.'" id="'.$hid.'">'; 131 $this->doc .= $this->_xmlEntities($text); 132 $this->doc .= "</a>"; 133 if ($conf['purplenumbers']) $this->doc .= " <a href='#$hid' class='pn' title='".$lang['sectionlink']."'>§</a>"; 134 $this->doc .= "</h$level>".DOKU_LF; 135 } 136 137 /** 138 * Section edit marker is replaced by an edit button when 139 * the page is editable. Replacement done in 'inc/html.php#html_secedit' 140 * 141 * @author Andreas Gohr <andi@splitbrain.org> 142 * @author Ben Coburn <btcoburn@silicodon.net> 143 */ 144 function section_edit($start, $end, $level, $name) { 145 global $conf; 146 147 if ($start!=-1 && $level<=$conf['maxseclevel']) { 148 $name = str_replace('"', '', $name); 149 $this->doc .= '<!-- SECTION "'.$name.'" ['.$start.'-'.(($end===0)?'':$end).'] -->'; 150 } 151 } 152 153 function section_open($level) { 154 $this->doc .= "<div class=\"level$level\">".DOKU_LF; 155 } 156 157 function section_close() { 158 $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 159 } 160 161 function cdata($text) { 162 $this->doc .= $this->_xmlEntities($text); 163 } 164 165 function p_open() { 166 $this->doc .= DOKU_LF.'<p'.$this->_getPurpleNumberID().'>'.DOKU_LF; 167 } 168 169 function p_close() { 170 $this->doc .= DOKU_LF.$this->_getPurpleNumberLink().'</p>'.DOKU_LF; 171 } 172 173 function linebreak() { 174 $this->doc .= '<br/>'.DOKU_LF; 175 } 176 177 function hr() { 178 $this->doc .= '<hr />'.DOKU_LF; 179 } 180 181 function strong_open() { 182 $this->doc .= '<strong>'; 183 } 184 185 function strong_close() { 186 $this->doc .= '</strong>'; 187 } 188 189 function emphasis_open() { 190 $this->doc .= '<em>'; 191 } 192 193 function emphasis_close() { 194 $this->doc .= '</em>'; 195 } 196 197 function underline_open() { 198 $this->doc .= '<em class="u">'; 199 } 200 201 function underline_close() { 202 $this->doc .= '</em>'; 203 } 204 205 function monospace_open() { 206 $this->doc .= '<code>'; 207 } 208 209 function monospace_close() { 210 $this->doc .= '</code>'; 211 } 212 213 function subscript_open() { 214 $this->doc .= '<sub>'; 215 } 216 217 function subscript_close() { 218 $this->doc .= '</sub>'; 219 } 220 221 function superscript_open() { 222 $this->doc .= '<sup>'; 223 } 224 225 function superscript_close() { 226 $this->doc .= '</sup>'; 227 } 228 229 function deleted_open() { 230 $this->doc .= '<del>'; 231 } 232 233 function deleted_close() { 234 $this->doc .= '</del>'; 235 } 236 237 /** 238 * Callback for footnote start syntax 239 * 240 * All following content will go to the footnote instead of 241 * the document. To achieve this the previous rendered content 242 * is moved to $store and $doc is cleared 243 * 244 * @author Andreas Gohr <andi@splitbrain.org> 245 */ 246 function footnote_open() { 247 248 // move current content to store and record footnote 249 $this->store = $this->doc; 250 $this->doc = ''; 251 } 252 253 /** 254 * Callback for footnote end syntax 255 * 256 * All rendered content is moved to the $footnotes array and the old 257 * content is restored from $store again 258 * 259 * @author Andreas Gohr 260 */ 261 function footnote_close() { 262 263 // recover footnote into the stack and restore old content 264 $footnote = $this->doc; 265 $this->doc = $this->store; 266 $this->store = ''; 267 268 // check to see if this footnote has been seen before 269 $i = array_search($footnote, $this->footnotes); 270 271 if ($i === false) { 272 // its a new footnote, add it to the $footnotes array 273 $id = count($this->footnotes)+1; 274 $this->footnotes[count($this->footnotes)] = $footnote; 275 } else { 276 // seen this one before, translate the index to an id and save a placeholder 277 $i++; 278 $id = count($this->footnotes)+1; 279 $this->footnotes[count($this->footnotes)] = "@@FNT".($i); 280 } 281 282 // output the footnote reference and link 283 $this->doc .= '<sup><a href="#fn__'.$id.'" name="fnt__'.$id.'" id="fnt__'.$id.'" class="fn_top">'.$id.')</a></sup>'; 284 } 285 286 function listu_open() { 287 $this->doc .= '<ul>'.DOKU_LF; 288 } 289 290 function listu_close() { 291 $this->doc .= '</ul>'.DOKU_LF; 292 } 293 294 function listo_open() { 295 $this->doc .= '<ol>'.DOKU_LF; 296 } 297 298 function listo_close() { 299 $this->doc .= '</ol>'.DOKU_LF; 300 } 301 302 function listitem_open($level) { 303 $this->doc .= '<li class="level'.$level.'"'.$this->_getPurpleNumberID().'>'; 304 } 305 306 function listitem_close() { 307 $this->doc .= '</li>'.DOKU_LF; 308 } 309 310 function listcontent_open() { 311 $this->doc .= '<div class="li">'; 312 } 313 314 function listcontent_close() { 315 $this->doc .= $this->_getPurpleNumberLink().'</div>'.DOKU_LF; 316 } 317 318 function unformatted($text) { 319 $this->doc .= $this->_xmlEntities($text); 320 } 321 322 /** 323 * Execute PHP code if allowed 324 * 325 * @param string $wrapper html element to wrap result if $conf['phpok'] is okff 326 * 327 * @author Andreas Gohr <andi@splitbrain.org> 328 */ 329 function php($text, $wrapper='code') { 330 global $conf; 331 332 if($conf['phpok']){ 333 ob_start(); 334 eval($text); 335 $this->doc .= ob_get_contents(); 336 ob_end_clean(); 337 } else { 338 $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 339 } 340 } 341 342 function phpblock($text) { 343 $this->php($text, 'pre'); 344 } 345 346 /** 347 * Insert HTML if allowed 348 * 349 * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff 350 * 351 * @author Andreas Gohr <andi@splitbrain.org> 352 */ 353 function html($text, $wrapper='code') { 354 global $conf; 355 356 if($conf['htmlok']){ 357 $this->doc .= $text; 358 } else { 359 $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 360 } 361 } 362 363 function htmlblock($text) { 364 $this->html($text, 'pre'); 365 } 366 367 function preformatted($text) { 368 $this->doc .= '<pre class="code"'.$this->_getPurpleNumberID().'>' . trim($this->_xmlEntities($text)) . $this->_getPurpleNumberLink(). '</pre>'. DOKU_LF; 369 } 370 371 function file($text) { 372 $this->doc .= '<pre class="file"'.$this->_getPurpleNumberID().'>' . trim($this->_xmlEntities($text)). $this->_getPurpleNumberLink(). '</pre>'. DOKU_LF; 373 } 374 375 function quote_open() { 376 $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 377 } 378 379 function quote_close() { 380 $this->doc .= '</div></blockquote>'.DOKU_LF; 381 } 382 383 /** 384 * Callback for code text 385 * 386 * Uses GeSHi to highlight language syntax 387 * 388 * @author Andreas Gohr <andi@splitbrain.org> 389 */ 390 function code($text, $language = NULL) { 391 global $conf; 392 393 if ( is_null($language) ) { 394 $this->preformatted($text); 395 } else { 396 $this->doc .= p_xhtml_cached_geshi($text, $language); 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 global $lang; 450 $this->doc .= $lang['singlequoteopening']; 451 } 452 453 function singlequoteclosing() { 454 global $lang; 455 $this->doc .= $lang['singlequoteclosing']; 456 } 457 458 function apostrophe() { 459 global $lang; 460 $this->doc .= $lang['apostrophe']; 461 } 462 463 function doublequoteopening() { 464 global $lang; 465 $this->doc .= $lang['doublequoteopening']; 466 } 467 468 function doublequoteclosing() { 469 global $lang; 470 $this->doc .= $lang['doublequoteclosing']; 471 } 472 473 /** 474 */ 475 function camelcaselink($link) { 476 $this->internallink($link,$link); 477 } 478 479 480 function locallink($hash, $name = NULL){ 481 global $ID; 482 $name = $this->_getLinkTitle($name, $hash, $isImage); 483 $hash = $this->_headerToLink($hash); 484 $title = $ID.' ↵'; 485 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 486 $this->doc .= $name; 487 $this->doc .= '</a>'; 488 } 489 490 /** 491 * Render an internal Wiki Link 492 * 493 * $search,$returnonly & $linktype are not for the renderer but are used 494 * elsewhere - no need to implement them in other renderers 495 * 496 * @author Andreas Gohr <andi@splitbrain.org> 497 */ 498 function internallink($id, $name = NULL, $search=NULL,$returnonly=false,$linktype='content') { 499 global $conf; 500 global $ID; 501 // default name is based on $id as given 502 $default = $this->_simpleTitle($id); 503 504 // now first resolve and clean up the $id 505 resolve_pageid(getNS($ID),$id,$exists); 506 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 507 if ( !$isImage ) { 508 if ( $exists ) { 509 $class='wikilink1'; 510 } else { 511 $class='wikilink2'; 512 $link['rel']='nofollow'; 513 } 514 } else { 515 $class='media'; 516 } 517 518 //keep hash anchor 519 list($id,$hash) = explode('#',$id,2); 520 if(!empty($hash)) $hash = $this->_headerToLink($hash); 521 522 //prepare for formating 523 $link['target'] = $conf['target']['wiki']; 524 $link['style'] = ''; 525 $link['pre'] = ''; 526 $link['suf'] = ''; 527 // highlight link to current page 528 if ($id == $ID) { 529 $link['pre'] = '<span class="curid">'; 530 $link['suf'] = '</span>'; 531 } 532 $link['more'] = ''; 533 $link['class'] = $class; 534 $link['url'] = wl($id); 535 $link['name'] = $name; 536 $link['title'] = $id; 537 //add search string 538 if($search){ 539 ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&'; 540 if(is_array($search)){ 541 $search = array_map('rawurlencode',$search); 542 $link['url'] .= 's[]='.join('&s[]=',$search); 543 }else{ 544 $link['url'] .= 's='.rawurlencode($search); 545 } 546 } 547 548 //keep hash 549 if($hash) $link['url'].='#'.$hash; 550 551 //output formatted 552 if($returnonly){ 553 return $this->_formatLink($link); 554 }else{ 555 $this->doc .= $this->_formatLink($link); 556 } 557 } 558 559 function externallink($url, $name = NULL) { 560 global $conf; 561 562 $name = $this->_getLinkTitle($name, $url, $isImage); 563 564 if ( !$isImage ) { 565 $class='urlextern'; 566 } else { 567 $class='media'; 568 } 569 570 //prepare for formating 571 $link['target'] = $conf['target']['extern']; 572 $link['style'] = ''; 573 $link['pre'] = ''; 574 $link['suf'] = ''; 575 $link['more'] = ''; 576 $link['class'] = $class; 577 $link['url'] = $url; 578 579 $link['name'] = $name; 580 $link['title'] = $this->_xmlEntities($url); 581 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 582 583 //output formatted 584 $this->doc .= $this->_formatLink($link); 585 } 586 587 /** 588 */ 589 function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { 590 global $conf; 591 592 $link = array(); 593 $link['target'] = $conf['target']['interwiki']; 594 $link['pre'] = ''; 595 $link['suf'] = ''; 596 $link['more'] = ''; 597 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 598 599 //get interwiki URL 600 $url = $this->_resolveInterWiki($wikiName,$wikiUri); 601 602 if ( !$isImage ) { 603 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName); 604 $link['class'] = "interwiki iw_$class"; 605 } else { 606 $link['class'] = 'media'; 607 } 608 609 //do we stay at the same server? Use local target 610 if( strpos($url,DOKU_URL) === 0 ){ 611 $link['target'] = $conf['target']['wiki']; 612 } 613 614 $link['url'] = $url; 615 $link['title'] = htmlspecialchars($link['url']); 616 617 //output formatted 618 $this->doc .= $this->_formatLink($link); 619 } 620 621 /** 622 */ 623 function windowssharelink($url, $name = NULL) { 624 global $conf; 625 global $lang; 626 //simple setup 627 $link['target'] = $conf['target']['windows']; 628 $link['pre'] = ''; 629 $link['suf'] = ''; 630 $link['style'] = ''; 631 632 $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 633 if ( !$isImage ) { 634 $link['class'] = 'windows'; 635 } else { 636 $link['class'] = 'media'; 637 } 638 639 640 $link['title'] = $this->_xmlEntities($url); 641 $url = str_replace('\\','/',$url); 642 $url = 'file:///'.$url; 643 $link['url'] = $url; 644 645 //output formatted 646 $this->doc .= $this->_formatLink($link); 647 } 648 649 function emaillink($address, $name = NULL) { 650 global $conf; 651 //simple setup 652 $link = array(); 653 $link['target'] = ''; 654 $link['pre'] = ''; 655 $link['suf'] = ''; 656 $link['style'] = ''; 657 $link['more'] = ''; 658 659 $name = $this->_getLinkTitle($name, $address, $isImage); 660 if ( !$isImage ) { 661 $link['class']='mail JSnocheck'; 662 } else { 663 $link['class']='media JSnocheck'; 664 } 665 666 $address = $this->_xmlEntities($address); 667 $address = obfuscate($address); 668 $title = $address; 669 670 if(empty($name)){ 671 $name = $address; 672 } 673#elseif($isImage{ 674# $name = $this->_xmlEntities($name); 675# } 676 677 if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 678 679 $link['url'] = 'mailto:'.$address; 680 $link['name'] = $name; 681 $link['title'] = $title; 682 683 //output formatted 684 $this->doc .= $this->_formatLink($link); 685 } 686 687 function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 688 $height=NULL, $cache=NULL, $linking=NULL) { 689 global $ID; 690 list($src,$hash) = explode('#',$src,2); 691 resolve_mediaid(getNS($ID),$src, $exists); 692 693 $noLink = false; 694 $render = ($linking == 'linkonly') ? false : true; 695 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 696 697 list($ext,$mime,$dl) = mimetype($src); 698 if(substr($mime,0,5) == 'image' && $render){ 699 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct')); 700 }elseif($mime == 'application/x-shockwave-flash' && $render){ 701 // don't link flash movies 702 $noLink = true; 703 }else{ 704 // add file icons 705 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 706 $link['class'] .= ' mediafile mf_'.$class; 707 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true); 708 } 709 710 if($hash) $link['url'] .= '#'.$hash; 711 712 //markup non existing files 713 if (!$exists) 714 $link['class'] .= ' wikilink2'; 715 716 //output formatted 717 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 718 else $this->doc .= $this->_formatLink($link); 719 } 720 721 function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 722 $height=NULL, $cache=NULL, $linking=NULL) { 723 list($src,$hash) = explode('#',$src,2); 724 $noLink = false; 725 $render = ($linking == 'linkonly') ? false : true; 726 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 727 728 $link['url'] = ml($src,array('cache'=>$cache)); 729 730 list($ext,$mime,$dl) = mimetype($src); 731 if(substr($mime,0,5) == 'image' && $render){ 732 // link only jpeg images 733 // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 734 }elseif($mime == 'application/x-shockwave-flash' && $render){ 735 // don't link flash movies 736 $noLink = true; 737 }else{ 738 // add file icons 739 $link['class'] .= ' mediafile mf_'.$ext; 740 } 741 742 if($hash) $link['url'] .= '#'.$hash; 743 744 //output formatted 745 if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 746 else $this->doc .= $this->_formatLink($link); 747 } 748 749 /** 750 * Renders an RSS feed 751 * 752 * @author Andreas Gohr <andi@splitbrain.org> 753 */ 754 function rss ($url,$params){ 755 global $lang; 756 global $conf; 757 758 require_once(DOKU_INC.'inc/FeedParser.php'); 759 $feed = new FeedParser(); 760 $feed->set_feed_url($url); 761 762 //disable warning while fetching 763 if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); } 764 $rc = $feed->init(); 765 if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); } 766 767 //decide on start and end 768 if($params['reverse']){ 769 $mod = -1; 770 $start = $feed->get_item_quantity()-1; 771 $end = $start - ($params['max']); 772 $end = ($end < -1) ? -1 : $end; 773 }else{ 774 $mod = 1; 775 $start = 0; 776 $end = $feed->get_item_quantity(); 777 $end = ($end > $params['max']) ? $params['max'] : $end;; 778 } 779 780 $this->doc .= '<ul class="rss">'; 781 if($rc){ 782 for ($x = $start; $x != $end; $x += $mod) { 783 $item = $feed->get_item($x); 784 $this->doc .= '<li><div class="li">'; 785 // support feeds without links 786 $lnkurl = $item->get_permalink(); 787 if($lnkurl){ 788 $this->externallink($item->get_permalink(), 789 $item->get_title()); 790 }else{ 791 $this->doc .= ' '.$item->get_title(); 792 } 793 if($params['author']){ 794 $author = $item->get_author(0); 795 if($author){ 796 $name = $author->get_name(); 797 if(!$name) $name = $author->get_email(); 798 if($name) $this->doc .= ' '.$lang['by'].' '.$name; 799 } 800 } 801 if($params['date']){ 802 $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 803 } 804 if($params['details']){ 805 $this->doc .= '<div class="detail">'; 806 if($conf['htmlok']){ 807 $this->doc .= $item->get_description(); 808 }else{ 809 $this->doc .= strip_tags($item->get_description()); 810 } 811 $this->doc .= '</div>'; 812 } 813 814 $this->doc .= '</div></li>'; 815 } 816 }else{ 817 $this->doc .= '<li><div class="li">'; 818 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 819 $this->externallink($url); 820 if($conf['allowdebug']){ 821 $this->doc .= '<!--'.hsc($feed->error).'-->'; 822 } 823 $this->doc .= '</div></li>'; 824 } 825 $this->doc .= '</ul>'; 826 } 827 828 // $numrows not yet implemented 829 function table_open($maxcols = NULL, $numrows = NULL){ 830 // initialize the row counter used for classes 831 $this->_counter['row_counter'] = 0; 832 $this->doc .= '<table class="inline"'.$this->_getPurpleNumberID().'>'.DOKU_LF; 833 } 834 835 function table_close(){ 836 $this->doc .= '</table>'.$this->_getPurpleNumberLink(1).DOKU_LF; 837 } 838 839 function tablerow_open(){ 840 // initialize the cell counter used for classes 841 $this->_counter['cell_counter'] = 0; 842 $class = 'row' . $this->_counter['row_counter']++; 843 $this->doc .= DOKU_TAB . '<tr class="'.$class.'">' . DOKU_LF . DOKU_TAB . DOKU_TAB; 844 } 845 846 function tablerow_close(){ 847 $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 848 } 849 850 function tableheader_open($colspan = 1, $align = NULL){ 851 $class = 'class="col' . $this->_counter['cell_counter']++; 852 if ( !is_null($align) ) { 853 $class .= ' '.$align.'align'; 854 } 855 $class .= '"'; 856 $this->doc .= '<th ' . $class; 857 if ( $colspan > 1 ) { 858 $this->_counter['cell_counter'] += $colspan-1; 859 $this->doc .= ' colspan="'.$colspan.'"'; 860 } 861 $this->doc .= '>'; 862 } 863 864 function tableheader_close(){ 865 $this->doc .= '</th>'; 866 } 867 868 function tablecell_open($colspan = 1, $align = NULL){ 869 $class = 'class="col' . $this->_counter['cell_counter']++; 870 if ( !is_null($align) ) { 871 $class .= ' '.$align.'align'; 872 } 873 $class .= '"'; 874 $this->doc .= '<td '.$class; 875 if ( $colspan > 1 ) { 876 $this->_counter['cell_counter'] += $colspan-1; 877 $this->doc .= ' colspan="'.$colspan.'"'; 878 } 879 $this->doc .= '>'; 880 } 881 882 function tablecell_close(){ 883 $this->doc .= '</td>'; 884 } 885 886 //---------------------------------------------------------- 887 // Utils 888 889 /** 890 * Build a link 891 * 892 * Assembles all parts defined in $link returns HTML for the link 893 * 894 * @author Andreas Gohr <andi@splitbrain.org> 895 */ 896 function _formatLink($link){ 897 //make sure the url is XHTML compliant (skip mailto) 898 if(substr($link['url'],0,7) != 'mailto:'){ 899 $link['url'] = str_replace('&','&',$link['url']); 900 $link['url'] = str_replace('&amp;','&',$link['url']); 901 } 902 //remove double encodings in titles 903 $link['title'] = str_replace('&amp;','&',$link['title']); 904 905 // be sure there are no bad chars in url or title 906 // (we can't do this for name because it can contain an img tag) 907 $link['url'] = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22')); 908 $link['title'] = strtr($link['title'],array('>'=>'>','<'=>'<','"'=>'"')); 909 910 $ret = ''; 911 $ret .= $link['pre']; 912 $ret .= '<a href="'.$link['url'].'"'; 913 if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 914 if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 915 if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 916 if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 917 if(!empty($link['rel'])) $ret .= ' rel="'.$link['rel'].'"'; 918 if(!empty($link['more'])) $ret .= ' '.$link['more']; 919 $ret .= '>'; 920 $ret .= $link['name']; 921 $ret .= '</a>'; 922 $ret .= $link['suf']; 923 return $ret; 924 } 925 926 /** 927 * Renders internal and external media 928 * 929 * @author Andreas Gohr <andi@splitbrain.org> 930 */ 931 function _media ($src, $title=NULL, $align=NULL, $width=NULL, 932 $height=NULL, $cache=NULL, $render = true) { 933 934 $ret = ''; 935 936 list($ext,$mime,$dl) = mimetype($src); 937 if(substr($mime,0,5) == 'image'){ 938 // first get the $title 939 if (!is_null($title)) { 940 $title = $this->_xmlEntities($title); 941 }elseif($ext == 'jpg' || $ext == 'jpeg'){ 942 //try to use the caption from IPTC/EXIF 943 require_once(DOKU_INC.'inc/JpegMeta.php'); 944 $jpeg =& new JpegMeta(mediaFN($src)); 945 if($jpeg !== false) $cap = $jpeg->getTitle(); 946 if($cap){ 947 $title = $this->_xmlEntities($cap); 948 } 949 } 950 if (!$render) { 951 // if the picture is not supposed to be rendered 952 // return the title of the picture 953 if (!$title) { 954 // just show the sourcename 955 $title = $this->_xmlEntities(basename(noNS($src))); 956 } 957 return $title; 958 } 959 //add image tag 960 $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"'; 961 $ret .= ' class="media'.$align.'"'; 962 963 // make left/right alignment for no-CSS view work (feeds) 964 if($align == 'right') $ret .= ' align="right"'; 965 if($align == 'left') $ret .= ' align="left"'; 966 967 if ($title) { 968 $ret .= ' title="' . $title . '"'; 969 $ret .= ' alt="' . $title .'"'; 970 }else{ 971 $ret .= ' alt=""'; 972 } 973 974 if ( !is_null($width) ) 975 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 976 977 if ( !is_null($height) ) 978 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 979 980 $ret .= ' />'; 981 982 }elseif($mime == 'application/x-shockwave-flash'){ 983 if (!$render) { 984 // if the flash is not supposed to be rendered 985 // return the title of the flash 986 if (!$title) { 987 // just show the sourcename 988 $title = basename(noNS($src)); 989 } 990 return $this->_xmlEntities($title); 991 } 992 993 $att = array(); 994 $att['class'] = "media$align"; 995 if($align == 'right') $att['align'] = 'right'; 996 if($align == 'left') $att['align'] = 'left'; 997 $ret .= html_flashobject(ml($src,array('cache'=>$cache)),$width,$height, 998 array('quality' => 'high'), 999 null, 1000 $att, 1001 $this->_xmlEntities($title)); 1002 }elseif($title){ 1003 // well at least we have a title to display 1004 $ret .= $this->_xmlEntities($title); 1005 }else{ 1006 // just show the sourcename 1007 $ret .= $this->_xmlEntities(basename(noNS($src))); 1008 } 1009 1010 return $ret; 1011 } 1012 1013 function _xmlEntities($string) { 1014 return htmlspecialchars($string,ENT_QUOTES,'UTF-8'); 1015 } 1016 1017 /** 1018 * Creates a linkid from a headline 1019 * 1020 * @param string $title The headline title 1021 * @param boolean $create Create a new unique ID? 1022 * @author Andreas Gohr <andi@splitbrain.org> 1023 */ 1024 function _headerToLink($title,$create=false) { 1025 if($create){ 1026 return sectionID($title,$this->headers); 1027 }else{ 1028 $check = false; 1029 return sectionID($title,$check); 1030 } 1031 } 1032 1033 /** 1034 * Construct a title and handle images in titles 1035 * 1036 * @author Harry Fuecks <hfuecks@gmail.com> 1037 */ 1038 function _getLinkTitle($title, $default, & $isImage, $id=NULL, $linktype='content') { 1039 global $conf; 1040 1041 $isImage = false; 1042 if ( is_null($title) || trim($title)=='') { 1043 if (useHeading($linktype) && $id) { 1044 $heading = p_get_first_heading($id,true); 1045 if ($heading) { 1046 return $this->_xmlEntities($heading); 1047 } 1048 } 1049 return $this->_xmlEntities($default); 1050 } else if ( is_array($title) ) { 1051 $isImage = true; 1052 return $this->_imageTitle($title); 1053 } else { 1054 return $this->_xmlEntities($title); 1055 } 1056 } 1057 1058 /** 1059 * Returns an HTML code for images used in link titles 1060 * 1061 * @todo Resolve namespace on internal images 1062 * @author Andreas Gohr <andi@splitbrain.org> 1063 */ 1064 function _imageTitle($img) { 1065 return $this->_media($img['src'], 1066 $img['title'], 1067 $img['align'], 1068 $img['width'], 1069 $img['height'], 1070 $img['cache']); 1071 } 1072 1073 /** 1074 * _getMediaLinkConf is a helperfunction to internalmedia() and externalmedia() 1075 * which returns a basic link to a media. 1076 * 1077 * @author Pierre Spring <pierre.spring@liip.ch> 1078 * @param string $src 1079 * @param string $title 1080 * @param string $align 1081 * @param string $width 1082 * @param string $height 1083 * @param string $cache 1084 * @param string $render 1085 * @access protected 1086 * @return array 1087 */ 1088 function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) 1089 { 1090 global $conf; 1091 1092 $link = array(); 1093 $link['class'] = 'media'; 1094 $link['style'] = ''; 1095 $link['pre'] = ''; 1096 $link['suf'] = ''; 1097 $link['more'] = ''; 1098 $link['target'] = $conf['target']['media']; 1099 $link['title'] = $this->_xmlEntities($src); 1100 $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1101 1102 return $link; 1103 } 1104 1105 /** 1106 * Builds unique Hierarchical ID: 1107 * If $conf['purplenumbers'] is 2, it is unique per site, 1108 * otherwise it is unique per page. 1109 * 1110 * @author Anika Henke <anika@selfthinker.org> 1111 */ 1112 function _getHID($noprefix=0) { 1113 global $conf; 1114 if ($noprefix) { 1115 $prefix = ''; 1116 } else if ($conf['purplenumbers']==2) { 1117 global $ID; 1118 $prefix = $ID.'.'; 1119 } else { 1120 $prefix = 'HID'; 1121 } 1122 return $prefix.rtrim(join('.',$this->node),'.0').rtrim(':'.$this->pnid,':0'); 1123 } 1124 1125 /** 1126 * Equips each designated element with a Purple Number (Hierarchical ID). 1127 * 1128 * @author Anika Henke <anika@selfthinker.org> 1129 */ 1130 function _getPurpleNumberID() { 1131 global $conf; 1132 $this->pnid++; 1133 if ($conf['purplenumbers']) { 1134 return ' id="'.$this->_getHID().'"'; 1135 } 1136 return ''; 1137 } 1138 1139 /** 1140 * Creates a link to the current Purple Number (Hierarchical ID). 1141 * If the link cannot be inside its corresponding element (e.g. tables), 1142 * $outside is set and p.pnlink is added around the link. 1143 * 1144 * @author Anika Henke <anika@selfthinker.org> 1145 */ 1146 function _getPurpleNumberLink($outside=0) { 1147 global $conf; 1148 if ($conf['purplenumbers']) { 1149 global $lang; 1150 $pnlink = '<a href="#'.$this->_getHID().'" class="pn" title="'.$lang['sectionlink'].'">¶</a>'; 1151 if ($outside) { 1152 return '<p class="pnlink">'.$pnlink.'</p>'; 1153 } 1154 return ' <!--PN-->'.$pnlink; 1155 } 1156 return ''; 1157 } 1158 1159} 1160 1161//Setup VIM: ex: et ts=4 enc=utf-8 : 1162