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 20/** 21 * The XHTML Renderer 22 * 23 * This is DokuWiki's main renderer used to display page content in the wiki 24 */ 25class Doku_Renderer_xhtml extends Doku_Renderer { 26 /** @var array store the table of contents */ 27 public $toc = array(); 28 29 /** @var array A stack of section edit data */ 30 protected $sectionedits = array(); 31 var $date_at = ''; // link pages and media against this revision 32 33 /** @var int last section edit id, used by startSectionEdit */ 34 protected $lastsecid = 0; 35 36 /** @var array the list of headers used to create unique link ids */ 37 protected $headers = array(); 38 39 /** @var array a list of footnotes, list starts at 1! */ 40 protected $footnotes = array(); 41 42 /** @var int current section level */ 43 protected $lastlevel = 0; 44 /** @var array section node tracker */ 45 protected $node = array(0, 0, 0, 0, 0); 46 47 /** @var string temporary $doc store */ 48 protected $store = ''; 49 50 /** @var array global counter, for table classes etc. */ 51 protected $_counter = array(); // 52 53 /** @var int counts the code and file blocks, used to provide download links */ 54 protected $_codeblock = 0; 55 56 /** @var array list of allowed URL schemes */ 57 protected $schemes = null; 58 59 /** 60 * Register a new edit section range 61 * 62 * @param $type string The section type identifier 63 * @param $title string The section title 64 * @param $start int The byte position for the edit start 65 * @return string A marker class for the starting HTML element 66 * @author Adrian Lang <lang@cosmocode.de> 67 */ 68 public function startSectionEdit($start, $type, $title = null) { 69 $this->sectionedits[] = array(++$this->lastsecid, $start, $type, $title); 70 return 'sectionedit'.$this->lastsecid; 71 } 72 73 /** 74 * Finish an edit section range 75 * 76 * @param $end int The byte position for the edit end; null for the rest of 77 * the page 78 * @author Adrian Lang <lang@cosmocode.de> 79 */ 80 public function finishSectionEdit($end = null) { 81 list($id, $start, $type, $title) = array_pop($this->sectionedits); 82 if(!is_null($end) && $end <= $start) { 83 return; 84 } 85 $this->doc .= "<!-- EDIT$id ".strtoupper($type).' '; 86 if(!is_null($title)) { 87 $this->doc .= '"'.str_replace('"', '', $title).'" '; 88 } 89 $this->doc .= "[$start-".(is_null($end) ? '' : $end).'] -->'; 90 } 91 92 /** 93 * Returns the format produced by this renderer. 94 * 95 * @return string always 'xhtml' 96 */ 97 function getFormat() { 98 return 'xhtml'; 99 } 100 101 /** 102 * Initialize the document 103 */ 104 function document_start() { 105 //reset some internals 106 $this->toc = array(); 107 $this->headers = array(); 108 } 109 110 /** 111 * Finalize the document 112 */ 113 function document_end() { 114 // Finish open section edits. 115 while(count($this->sectionedits) > 0) { 116 if($this->sectionedits[count($this->sectionedits) - 1][1] <= 1) { 117 // If there is only one section, do not write a section edit 118 // marker. 119 array_pop($this->sectionedits); 120 } else { 121 $this->finishSectionEdit(); 122 } 123 } 124 125 if(count($this->footnotes) > 0) { 126 $this->doc .= '<div class="footnotes">'.DOKU_LF; 127 128 foreach($this->footnotes as $id => $footnote) { 129 // check its not a placeholder that indicates actual footnote text is elsewhere 130 if(substr($footnote, 0, 5) != "@@FNT") { 131 132 // open the footnote and set the anchor and backlink 133 $this->doc .= '<div class="fn">'; 134 $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">'; 135 $this->doc .= $id.')</a></sup> '.DOKU_LF; 136 137 // get any other footnotes that use the same markup 138 $alt = array_keys($this->footnotes, "@@FNT$id"); 139 140 if(count($alt)) { 141 foreach($alt as $ref) { 142 // set anchor and backlink for the other footnotes 143 $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">'; 144 $this->doc .= ($ref).')</a></sup> '.DOKU_LF; 145 } 146 } 147 148 // add footnote markup and close this footnote 149 $this->doc .= $footnote; 150 $this->doc .= '</div>'.DOKU_LF; 151 } 152 } 153 $this->doc .= '</div>'.DOKU_LF; 154 } 155 156 // Prepare the TOC 157 global $conf; 158 if($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']) { 159 global $TOC; 160 $TOC = $this->toc; 161 } 162 163 // make sure there are no empty paragraphs 164 $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 165 } 166 167 /** 168 * Add an item to the TOC 169 * 170 * @param string $id the hash link 171 * @param string $text the text to display 172 * @param int $level the nesting level 173 */ 174 function toc_additem($id, $text, $level) { 175 global $conf; 176 177 //handle TOC 178 if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 179 $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 180 } 181 } 182 183 /** 184 * Render a heading 185 * 186 * @param string $text the text to display 187 * @param int $level header level 188 * @param int $pos byte position in the original source 189 */ 190 function header($text, $level, $pos) { 191 global $conf; 192 193 if(!$text) return; //skip empty headlines 194 195 $hid = $this->_headerToLink($text, true); 196 197 //only add items within configured levels 198 $this->toc_additem($hid, $text, $level); 199 200 // adjust $node to reflect hierarchy of levels 201 $this->node[$level - 1]++; 202 if($level < $this->lastlevel) { 203 for($i = 0; $i < $this->lastlevel - $level; $i++) { 204 $this->node[$this->lastlevel - $i - 1] = 0; 205 } 206 } 207 $this->lastlevel = $level; 208 209 if($level <= $conf['maxseclevel'] && 210 count($this->sectionedits) > 0 && 211 $this->sectionedits[count($this->sectionedits) - 1][2] === 'section' 212 ) { 213 $this->finishSectionEdit($pos - 1); 214 } 215 216 // write the header 217 $this->doc .= DOKU_LF.'<h'.$level; 218 if($level <= $conf['maxseclevel']) { 219 $this->doc .= ' class="'.$this->startSectionEdit($pos, 'section', $text).'"'; 220 } 221 $this->doc .= ' id="'.$hid.'">'; 222 $this->doc .= $this->_xmlEntities($text); 223 $this->doc .= "</h$level>".DOKU_LF; 224 } 225 226 /** 227 * Open a new section 228 * 229 * @param int $level section level (as determined by the previous header) 230 */ 231 function section_open($level) { 232 $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; 233 } 234 235 /** 236 * Close the current section 237 */ 238 function section_close() { 239 $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 240 } 241 242 /** 243 * Render plain text data 244 * 245 * @param $text 246 */ 247 function cdata($text) { 248 $this->doc .= $this->_xmlEntities($text); 249 } 250 251 /** 252 * Open a paragraph 253 */ 254 function p_open() { 255 $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 256 } 257 258 /** 259 * Close a paragraph 260 */ 261 function p_close() { 262 $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 263 } 264 265 /** 266 * Create a line break 267 */ 268 function linebreak() { 269 $this->doc .= '<br/>'.DOKU_LF; 270 } 271 272 /** 273 * Create a horizontal line 274 */ 275 function hr() { 276 $this->doc .= '<hr />'.DOKU_LF; 277 } 278 279 /** 280 * Start strong (bold) formatting 281 */ 282 function strong_open() { 283 $this->doc .= '<strong>'; 284 } 285 286 /** 287 * Stop strong (bold) formatting 288 */ 289 function strong_close() { 290 $this->doc .= '</strong>'; 291 } 292 293 /** 294 * Start emphasis (italics) formatting 295 */ 296 function emphasis_open() { 297 $this->doc .= '<em>'; 298 } 299 300 /** 301 * Stop emphasis (italics) formatting 302 */ 303 function emphasis_close() { 304 $this->doc .= '</em>'; 305 } 306 307 /** 308 * Start underline formatting 309 */ 310 function underline_open() { 311 $this->doc .= '<em class="u">'; 312 } 313 314 /** 315 * Stop underline formatting 316 */ 317 function underline_close() { 318 $this->doc .= '</em>'; 319 } 320 321 /** 322 * Start monospace formatting 323 */ 324 function monospace_open() { 325 $this->doc .= '<code>'; 326 } 327 328 /** 329 * Stop monospace formatting 330 */ 331 function monospace_close() { 332 $this->doc .= '</code>'; 333 } 334 335 /** 336 * Start a subscript 337 */ 338 function subscript_open() { 339 $this->doc .= '<sub>'; 340 } 341 342 /** 343 * Stop a subscript 344 */ 345 function subscript_close() { 346 $this->doc .= '</sub>'; 347 } 348 349 /** 350 * Start a superscript 351 */ 352 function superscript_open() { 353 $this->doc .= '<sup>'; 354 } 355 356 /** 357 * Stop a superscript 358 */ 359 function superscript_close() { 360 $this->doc .= '</sup>'; 361 } 362 363 /** 364 * Start deleted (strike-through) formatting 365 */ 366 function deleted_open() { 367 $this->doc .= '<del>'; 368 } 369 370 /** 371 * Stop deleted (strike-through) formatting 372 */ 373 function deleted_close() { 374 $this->doc .= '</del>'; 375 } 376 377 /** 378 * Callback for footnote start syntax 379 * 380 * All following content will go to the footnote instead of 381 * the document. To achieve this the previous rendered content 382 * is moved to $store and $doc is cleared 383 * 384 * @author Andreas Gohr <andi@splitbrain.org> 385 */ 386 function footnote_open() { 387 388 // move current content to store and record footnote 389 $this->store = $this->doc; 390 $this->doc = ''; 391 } 392 393 /** 394 * Callback for footnote end syntax 395 * 396 * All rendered content is moved to the $footnotes array and the old 397 * content is restored from $store again 398 * 399 * @author Andreas Gohr 400 */ 401 function footnote_close() { 402 /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 403 static $fnid = 0; 404 // assign new footnote id (we start at 1) 405 $fnid++; 406 407 // recover footnote into the stack and restore old content 408 $footnote = $this->doc; 409 $this->doc = $this->store; 410 $this->store = ''; 411 412 // check to see if this footnote has been seen before 413 $i = array_search($footnote, $this->footnotes); 414 415 if($i === false) { 416 // its a new footnote, add it to the $footnotes array 417 $this->footnotes[$fnid] = $footnote; 418 } else { 419 // seen this one before, save a placeholder 420 $this->footnotes[$fnid] = "@@FNT".($i); 421 } 422 423 // output the footnote reference and link 424 $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>'; 425 } 426 427 /** 428 * Open an unordered list 429 */ 430 function listu_open() { 431 $this->doc .= '<ul>'.DOKU_LF; 432 } 433 434 /** 435 * Close an unordered list 436 */ 437 function listu_close() { 438 $this->doc .= '</ul>'.DOKU_LF; 439 } 440 441 /** 442 * Open an ordered list 443 */ 444 function listo_open() { 445 $this->doc .= '<ol>'.DOKU_LF; 446 } 447 448 /** 449 * Close an ordered list 450 */ 451 function listo_close() { 452 $this->doc .= '</ol>'.DOKU_LF; 453 } 454 455 /** 456 * Open a list item 457 * 458 * @param int $level the nesting level 459 */ 460 function listitem_open($level) { 461 $this->doc .= '<li class="level'.$level.'">'; 462 } 463 464 /** 465 * Close a list item 466 */ 467 function listitem_close() { 468 $this->doc .= '</li>'.DOKU_LF; 469 } 470 471 /** 472 * Start the content of a list item 473 */ 474 function listcontent_open() { 475 $this->doc .= '<div class="li">'; 476 } 477 478 /** 479 * Stop the content of a list item 480 */ 481 function listcontent_close() { 482 $this->doc .= '</div>'.DOKU_LF; 483 } 484 485 /** 486 * Output unformatted $text 487 * 488 * Defaults to $this->cdata() 489 * 490 * @param string $text 491 */ 492 function unformatted($text) { 493 $this->doc .= $this->_xmlEntities($text); 494 } 495 496 /** 497 * Execute PHP code if allowed 498 * 499 * @param string $text PHP code that is either executed or printed 500 * @param string $wrapper html element to wrap result if $conf['phpok'] is okff 501 * 502 * @author Andreas Gohr <andi@splitbrain.org> 503 */ 504 function php($text, $wrapper = 'code') { 505 global $conf; 506 507 if($conf['phpok']) { 508 ob_start(); 509 eval($text); 510 $this->doc .= ob_get_contents(); 511 ob_end_clean(); 512 } else { 513 $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 514 } 515 } 516 517 /** 518 * Output block level PHP code 519 * 520 * If $conf['phpok'] is true this should evaluate the given code and append the result 521 * to $doc 522 * 523 * @param string $text The PHP code 524 */ 525 function phpblock($text) { 526 $this->php($text, 'pre'); 527 } 528 529 /** 530 * Insert HTML if allowed 531 * 532 * @param string $text html text 533 * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff 534 * 535 * @author Andreas Gohr <andi@splitbrain.org> 536 */ 537 function html($text, $wrapper = 'code') { 538 global $conf; 539 540 if($conf['htmlok']) { 541 $this->doc .= $text; 542 } else { 543 $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 544 } 545 } 546 547 /** 548 * Output raw block-level HTML 549 * 550 * If $conf['htmlok'] is true this should add the code as is to $doc 551 * 552 * @param string $text The HTML 553 */ 554 function htmlblock($text) { 555 $this->html($text, 'pre'); 556 } 557 558 /** 559 * Start a block quote 560 */ 561 function quote_open() { 562 $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 563 } 564 565 /** 566 * Stop a block quote 567 */ 568 function quote_close() { 569 $this->doc .= '</div></blockquote>'.DOKU_LF; 570 } 571 572 /** 573 * Output preformatted text 574 * 575 * @param string $text 576 */ 577 function preformatted($text) { 578 $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; 579 } 580 581 /** 582 * Display text as file content, optionally syntax highlighted 583 * 584 * @param string $text text to show 585 * @param string $language programming language to use for syntax highlighting 586 * @param string $filename file path label 587 */ 588 function file($text, $language = null, $filename = null) { 589 $this->_highlight('file', $text, $language, $filename); 590 } 591 592 /** 593 * Display text as code content, optionally syntax highlighted 594 * 595 * @param string $text text to show 596 * @param string $language programming language to use for syntax highlighting 597 * @param string $filename file path label 598 */ 599 function code($text, $language = null, $filename = null) { 600 $this->_highlight('code', $text, $language, $filename); 601 } 602 603 /** 604 * Use GeSHi to highlight language syntax in code and file blocks 605 * 606 * @author Andreas Gohr <andi@splitbrain.org> 607 * @param string $type code|file 608 * @param string $text text to show 609 * @param string $language programming language to use for syntax highlighting 610 * @param string $filename file path label 611 */ 612 function _highlight($type, $text, $language = null, $filename = null) { 613 global $ID; 614 global $lang; 615 616 if($filename) { 617 // add icon 618 list($ext) = mimetype($filename, false); 619 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 620 $class = 'mediafile mf_'.$class; 621 622 $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 623 $this->doc .= '<dt><a href="'.exportlink($ID, 'code', array('codeblock' => $this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">'; 624 $this->doc .= hsc($filename); 625 $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 626 } 627 628 if($text{0} == "\n") { 629 $text = substr($text, 1); 630 } 631 if(substr($text, -1) == "\n") { 632 $text = substr($text, 0, -1); 633 } 634 635 if(is_null($language)) { 636 $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF; 637 } else { 638 $class = 'code'; //we always need the code class to make the syntax highlighting apply 639 if($type != 'code') $class .= ' '.$type; 640 641 $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '').'</pre>'.DOKU_LF; 642 } 643 644 if($filename) { 645 $this->doc .= '</dd></dl>'.DOKU_LF; 646 } 647 648 $this->_codeblock++; 649 } 650 651 /** 652 * Format an acronym 653 * 654 * Uses $this->acronyms 655 * 656 * @param string $acronym 657 */ 658 function acronym($acronym) { 659 660 if(array_key_exists($acronym, $this->acronyms)) { 661 662 $title = $this->_xmlEntities($this->acronyms[$acronym]); 663 664 $this->doc .= '<abbr title="'.$title 665 .'">'.$this->_xmlEntities($acronym).'</abbr>'; 666 667 } else { 668 $this->doc .= $this->_xmlEntities($acronym); 669 } 670 } 671 672 /** 673 * Format a smiley 674 * 675 * Uses $this->smiley 676 * 677 * @param string $smiley 678 */ 679 function smiley($smiley) { 680 if(array_key_exists($smiley, $this->smileys)) { 681 $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley]. 682 '" class="icon" alt="'. 683 $this->_xmlEntities($smiley).'" />'; 684 } else { 685 $this->doc .= $this->_xmlEntities($smiley); 686 } 687 } 688 689 /** 690 * Format an entity 691 * 692 * Entities are basically small text replacements 693 * 694 * Uses $this->entities 695 * 696 * @param string $entity 697 */ 698 function entity($entity) { 699 if(array_key_exists($entity, $this->entities)) { 700 $this->doc .= $this->entities[$entity]; 701 } else { 702 $this->doc .= $this->_xmlEntities($entity); 703 } 704 } 705 706 /** 707 * Typographically format a multiply sign 708 * 709 * Example: ($x=640, $y=480) should result in "640×480" 710 * 711 * @param string|int $x first value 712 * @param string|int $y second value 713 */ 714 function multiplyentity($x, $y) { 715 $this->doc .= "$x×$y"; 716 } 717 718 /** 719 * Render an opening single quote char (language specific) 720 */ 721 function singlequoteopening() { 722 global $lang; 723 $this->doc .= $lang['singlequoteopening']; 724 } 725 726 /** 727 * Render a closing single quote char (language specific) 728 */ 729 function singlequoteclosing() { 730 global $lang; 731 $this->doc .= $lang['singlequoteclosing']; 732 } 733 734 /** 735 * Render an apostrophe char (language specific) 736 */ 737 function apostrophe() { 738 global $lang; 739 $this->doc .= $lang['apostrophe']; 740 } 741 742 /** 743 * Render an opening double quote char (language specific) 744 */ 745 function doublequoteopening() { 746 global $lang; 747 $this->doc .= $lang['doublequoteopening']; 748 } 749 750 /** 751 * Render an closinging double quote char (language specific) 752 */ 753 function doublequoteclosing() { 754 global $lang; 755 $this->doc .= $lang['doublequoteclosing']; 756 } 757 758 /** 759 * Render a CamelCase link 760 * 761 * @param string $link The link name 762 * @see http://en.wikipedia.org/wiki/CamelCase 763 */ 764 function camelcaselink($link) { 765 $this->internallink($link, $link); 766 } 767 768 /** 769 * Render a page local link 770 * 771 * @param string $hash hash link identifier 772 * @param string $name name for the link 773 */ 774 function locallink($hash, $name = null) { 775 global $ID; 776 $name = $this->_getLinkTitle($name, $hash, $isImage); 777 $hash = $this->_headerToLink($hash); 778 $title = $ID.' ↵'; 779 $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 780 $this->doc .= $name; 781 $this->doc .= '</a>'; 782 } 783 784 /** 785 * Render an internal Wiki Link 786 * 787 * $search,$returnonly & $linktype are not for the renderer but are used 788 * elsewhere - no need to implement them in other renderers 789 * 790 * @author Andreas Gohr <andi@splitbrain.org> 791 * @param string $id pageid 792 * @param string|null $name link name 793 * @param string|null $search adds search url param 794 * @param bool $returnonly whether to return html or write to doc attribute 795 * @param string $linktype type to set use of headings 796 * @return void|string writes to doc attribute or returns html depends on $returnonly 797 */ 798 function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { 799 global $conf; 800 global $ID; 801 global $INFO; 802 803 $params = ''; 804 $parts = explode('?', $id, 2); 805 if(count($parts) === 2) { 806 $id = $parts[0]; 807 $params = $parts[1]; 808 } 809 810 // For empty $id we need to know the current $ID 811 // We need this check because _simpleTitle needs 812 // correct $id and resolve_pageid() use cleanID($id) 813 // (some things could be lost) 814 if($id === '') { 815 $id = $ID; 816 } 817 818 // default name is based on $id as given 819 $default = $this->_simpleTitle($id); 820 821 // now first resolve and clean up the $id 822 resolve_pageid(getNS($ID), $id, $exists, $this->date_at, true); 823 824 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 825 if(!$isImage) { 826 if($exists) { 827 $class = 'wikilink1'; 828 } else { 829 $class = 'wikilink2'; 830 $link['rel'] = 'nofollow'; 831 } 832 } else { 833 $class = 'media'; 834 } 835 836 //keep hash anchor 837 @list($id, $hash) = explode('#', $id, 2); 838 if(!empty($hash)) $hash = $this->_headerToLink($hash); 839 840 //prepare for formating 841 $link['target'] = $conf['target']['wiki']; 842 $link['style'] = ''; 843 $link['pre'] = ''; 844 $link['suf'] = ''; 845 // highlight link to current page 846 if($id == $INFO['id']) { 847 $link['pre'] = '<span class="curid">'; 848 $link['suf'] = '</span>'; 849 } 850 $link['more'] = ''; 851 $link['class'] = $class; 852 if($this->date_at) { 853 $params['at'] = $this->date_at; 854 } 855 $link['url'] = wl($id, $params); 856 $link['name'] = $name; 857 $link['title'] = $id; 858 //add search string 859 if($search) { 860 ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 861 if(is_array($search)) { 862 $search = array_map('rawurlencode', $search); 863 $link['url'] .= 's[]='.join('&s[]=', $search); 864 } else { 865 $link['url'] .= 's='.rawurlencode($search); 866 } 867 } 868 869 //keep hash 870 if($hash) $link['url'] .= '#'.$hash; 871 872 //output formatted 873 if($returnonly) { 874 return $this->_formatLink($link); 875 } else { 876 $this->doc .= $this->_formatLink($link); 877 } 878 } 879 880 /** 881 * Render an external link 882 * 883 * @param string $url full URL with scheme 884 * @param string|array $name name for the link, array for media file 885 */ 886 function externallink($url, $name = null) { 887 global $conf; 888 889 $name = $this->_getLinkTitle($name, $url, $isImage); 890 891 // url might be an attack vector, only allow registered protocols 892 if(is_null($this->schemes)) $this->schemes = getSchemes(); 893 list($scheme) = explode('://', $url); 894 $scheme = strtolower($scheme); 895 if(!in_array($scheme, $this->schemes)) $url = ''; 896 897 // is there still an URL? 898 if(!$url) { 899 $this->doc .= $name; 900 return; 901 } 902 903 // set class 904 if(!$isImage) { 905 $class = 'urlextern'; 906 } else { 907 $class = 'media'; 908 } 909 910 //prepare for formating 911 $link['target'] = $conf['target']['extern']; 912 $link['style'] = ''; 913 $link['pre'] = ''; 914 $link['suf'] = ''; 915 $link['more'] = ''; 916 $link['class'] = $class; 917 $link['url'] = $url; 918 919 $link['name'] = $name; 920 $link['title'] = $this->_xmlEntities($url); 921 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 922 923 //output formatted 924 $this->doc .= $this->_formatLink($link); 925 } 926 927 /** 928 * Render an interwiki link 929 * 930 * You may want to use $this->_resolveInterWiki() here 931 * 932 * @param string $match original link - probably not much use 933 * @param string|array $name name for the link, array for media file 934 * @param string $wikiName indentifier (shortcut) for the remote wiki 935 * @param string $wikiUri the fragment parsed from the original link 936 */ 937 function interwikilink($match, $name = null, $wikiName, $wikiUri) { 938 global $conf; 939 940 $link = array(); 941 $link['target'] = $conf['target']['interwiki']; 942 $link['pre'] = ''; 943 $link['suf'] = ''; 944 $link['more'] = ''; 945 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 946 947 //get interwiki URL 948 $exists = null; 949 $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 950 951 if(!$isImage) { 952 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 953 $link['class'] = "interwiki iw_$class"; 954 } else { 955 $link['class'] = 'media'; 956 } 957 958 //do we stay at the same server? Use local target 959 if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 960 $link['target'] = $conf['target']['wiki']; 961 } 962 if($exists !== null && !$isImage) { 963 if($exists) { 964 $link['class'] .= ' wikilink1'; 965 } else { 966 $link['class'] .= ' wikilink2'; 967 $link['rel'] = 'nofollow'; 968 } 969 } 970 971 $link['url'] = $url; 972 $link['title'] = htmlspecialchars($link['url']); 973 974 //output formatted 975 $this->doc .= $this->_formatLink($link); 976 } 977 978 /** 979 * Link to windows share 980 * 981 * @param string $url the link 982 * @param string|array $name name for the link, array for media file 983 */ 984 function windowssharelink($url, $name = null) { 985 global $conf; 986 987 //simple setup 988 $link['target'] = $conf['target']['windows']; 989 $link['pre'] = ''; 990 $link['suf'] = ''; 991 $link['style'] = ''; 992 993 $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 994 if(!$isImage) { 995 $link['class'] = 'windows'; 996 } else { 997 $link['class'] = 'media'; 998 } 999 1000 $link['title'] = $this->_xmlEntities($url); 1001 $url = str_replace('\\', '/', $url); 1002 $url = ltrim($url,'/'); 1003 $url = 'file:///'.$url; 1004 $link['url'] = $url; 1005 1006 //output formatted 1007 $this->doc .= $this->_formatLink($link); 1008 } 1009 1010 /** 1011 * Render a linked E-Mail Address 1012 * 1013 * Honors $conf['mailguard'] setting 1014 * 1015 * @param string $address Email-Address 1016 * @param string|array $name name for the link, array for media file 1017 */ 1018 function emaillink($address, $name = null) { 1019 global $conf; 1020 //simple setup 1021 $link = array(); 1022 $link['target'] = ''; 1023 $link['pre'] = ''; 1024 $link['suf'] = ''; 1025 $link['style'] = ''; 1026 $link['more'] = ''; 1027 1028 $name = $this->_getLinkTitle($name, '', $isImage); 1029 if(!$isImage) { 1030 $link['class'] = 'mail'; 1031 } else { 1032 $link['class'] = 'media'; 1033 } 1034 1035 $address = $this->_xmlEntities($address); 1036 $address = obfuscate($address); 1037 $title = $address; 1038 1039 if(empty($name)) { 1040 $name = $address; 1041 } 1042 1043 if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1044 1045 $link['url'] = 'mailto:'.$address; 1046 $link['name'] = $name; 1047 $link['title'] = $title; 1048 1049 //output formatted 1050 $this->doc .= $this->_formatLink($link); 1051 } 1052 1053 /** 1054 * Render an internal media file 1055 * 1056 * @param string $src media ID 1057 * @param string $title descriptive text 1058 * @param string $align left|center|right 1059 * @param int $width width of media in pixel 1060 * @param int $height height of media in pixel 1061 * @param string $cache cache|recache|nocache 1062 * @param string $linking linkonly|detail|nolink 1063 * @param bool $return return HTML instead of adding to $doc 1064 * @return void|string 1065 */ 1066 function internalmedia($src, $title = null, $align = null, $width = null, 1067 $height = null, $cache = null, $linking = null, $return = false) { 1068 global $ID; 1069 list($src, $hash) = explode('#', $src, 2); 1070 resolve_mediaid(getNS($ID), $src, $exists, $this->date_at, true); 1071 1072 $noLink = false; 1073 $render = ($linking == 'linkonly') ? false : true; 1074 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1075 1076 list($ext, $mime) = mimetype($src, false); 1077 if(substr($mime, 0, 5) == 'image' && $render) { 1078 $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache, 'rev'=>$this->_getLastMediaRevisionAt($src)), ($linking == 'direct')); 1079 } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 1080 // don't link movies 1081 $noLink = true; 1082 } else { 1083 // add file icons 1084 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 1085 $link['class'] .= ' mediafile mf_'.$class; 1086 $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache , 'rev'=>$this->_getLastMediaRevisionAt($src)), true); 1087 if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; 1088 } 1089 1090 if($hash) $link['url'] .= '#'.$hash; 1091 1092 //markup non existing files 1093 if(!$exists) { 1094 $link['class'] .= ' wikilink2'; 1095 } 1096 1097 //output formatted 1098 if($return) { 1099 if($linking == 'nolink' || $noLink) return $link['name']; 1100 else return $this->_formatLink($link); 1101 } else { 1102 if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 1103 else $this->doc .= $this->_formatLink($link); 1104 } 1105 } 1106 1107 /** 1108 * Render an external media file 1109 * 1110 * @param string $src full media URL 1111 * @param string $title descriptive text 1112 * @param string $align left|center|right 1113 * @param int $width width of media in pixel 1114 * @param int $height height of media in pixel 1115 * @param string $cache cache|recache|nocache 1116 * @param string $linking linkonly|detail|nolink 1117 * @param bool $return return HTML instead of adding to $doc 1118 */ 1119 function externalmedia($src, $title = null, $align = null, $width = null, 1120 $height = null, $cache = null, $linking = null, $return = false) { 1121 list($src, $hash) = explode('#', $src, 2); 1122 $noLink = false; 1123 $render = ($linking == 'linkonly') ? false : true; 1124 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1125 1126 $link['url'] = ml($src, array('cache' => $cache)); 1127 1128 list($ext, $mime) = mimetype($src, false); 1129 if(substr($mime, 0, 5) == 'image' && $render) { 1130 // link only jpeg images 1131 // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1132 } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 1133 // don't link movies 1134 $noLink = true; 1135 } else { 1136 // add file icons 1137 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 1138 $link['class'] .= ' mediafile mf_'.$class; 1139 } 1140 1141 if($hash) $link['url'] .= '#'.$hash; 1142 1143 //output formatted 1144 if($return) { 1145 if($linking == 'nolink' || $noLink) return $link['name']; 1146 else return $this->_formatLink($link); 1147 } else { 1148 if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 1149 else $this->doc .= $this->_formatLink($link); 1150 } 1151 } 1152 1153 /** 1154 * Renders an RSS feed 1155 * 1156 * @author Andreas Gohr <andi@splitbrain.org> 1157 */ 1158 function rss($url, $params) { 1159 global $lang; 1160 global $conf; 1161 1162 require_once(DOKU_INC.'inc/FeedParser.php'); 1163 $feed = new FeedParser(); 1164 $feed->set_feed_url($url); 1165 1166 //disable warning while fetching 1167 if(!defined('DOKU_E_LEVEL')) { 1168 $elvl = error_reporting(E_ERROR); 1169 } 1170 $rc = $feed->init(); 1171 if(isset($elvl)) { 1172 error_reporting($elvl); 1173 } 1174 1175 //decide on start and end 1176 if($params['reverse']) { 1177 $mod = -1; 1178 $start = $feed->get_item_quantity() - 1; 1179 $end = $start - ($params['max']); 1180 $end = ($end < -1) ? -1 : $end; 1181 } else { 1182 $mod = 1; 1183 $start = 0; 1184 $end = $feed->get_item_quantity(); 1185 $end = ($end > $params['max']) ? $params['max'] : $end; 1186 } 1187 1188 $this->doc .= '<ul class="rss">'; 1189 if($rc) { 1190 for($x = $start; $x != $end; $x += $mod) { 1191 $item = $feed->get_item($x); 1192 $this->doc .= '<li><div class="li">'; 1193 // support feeds without links 1194 $lnkurl = $item->get_permalink(); 1195 if($lnkurl) { 1196 // title is escaped by SimplePie, we unescape here because it 1197 // is escaped again in externallink() FS#1705 1198 $this->externallink( 1199 $item->get_permalink(), 1200 html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') 1201 ); 1202 } else { 1203 $this->doc .= ' '.$item->get_title(); 1204 } 1205 if($params['author']) { 1206 $author = $item->get_author(0); 1207 if($author) { 1208 $name = $author->get_name(); 1209 if(!$name) $name = $author->get_email(); 1210 if($name) $this->doc .= ' '.$lang['by'].' '.$name; 1211 } 1212 } 1213 if($params['date']) { 1214 $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 1215 } 1216 if($params['details']) { 1217 $this->doc .= '<div class="detail">'; 1218 if($conf['htmlok']) { 1219 $this->doc .= $item->get_description(); 1220 } else { 1221 $this->doc .= strip_tags($item->get_description()); 1222 } 1223 $this->doc .= '</div>'; 1224 } 1225 1226 $this->doc .= '</div></li>'; 1227 } 1228 } else { 1229 $this->doc .= '<li><div class="li">'; 1230 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 1231 $this->externallink($url); 1232 if($conf['allowdebug']) { 1233 $this->doc .= '<!--'.hsc($feed->error).'-->'; 1234 } 1235 $this->doc .= '</div></li>'; 1236 } 1237 $this->doc .= '</ul>'; 1238 } 1239 1240 /** 1241 * Start a table 1242 * 1243 * @param int $maxcols maximum number of columns 1244 * @param int $numrows NOT IMPLEMENTED 1245 * @param int $pos byte position in the original source 1246 */ 1247 function table_open($maxcols = null, $numrows = null, $pos = null) { 1248 // initialize the row counter used for classes 1249 $this->_counter['row_counter'] = 0; 1250 $class = 'table'; 1251 if($pos !== null) { 1252 $class .= ' '.$this->startSectionEdit($pos, 'table'); 1253 } 1254 $this->doc .= '<div class="'.$class.'"><table class="inline">'. 1255 DOKU_LF; 1256 } 1257 1258 /** 1259 * Close a table 1260 * 1261 * @param int $pos byte position in the original source 1262 */ 1263 function table_close($pos = null) { 1264 $this->doc .= '</table></div>'.DOKU_LF; 1265 if($pos !== null) { 1266 $this->finishSectionEdit($pos); 1267 } 1268 } 1269 1270 /** 1271 * Open a table header 1272 */ 1273 function tablethead_open() { 1274 $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 1275 } 1276 1277 /** 1278 * Close a table header 1279 */ 1280 function tablethead_close() { 1281 $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 1282 } 1283 1284 /** 1285 * Open a table row 1286 */ 1287 function tablerow_open() { 1288 // initialize the cell counter used for classes 1289 $this->_counter['cell_counter'] = 0; 1290 $class = 'row'.$this->_counter['row_counter']++; 1291 $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; 1292 } 1293 1294 /** 1295 * Close a table row 1296 */ 1297 function tablerow_close() { 1298 $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 1299 } 1300 1301 /** 1302 * Open a table header cell 1303 * 1304 * @param int $colspan 1305 * @param string $align left|center|right 1306 * @param int $rowspan 1307 */ 1308 function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { 1309 $class = 'class="col'.$this->_counter['cell_counter']++; 1310 if(!is_null($align)) { 1311 $class .= ' '.$align.'align'; 1312 } 1313 $class .= '"'; 1314 $this->doc .= '<th '.$class; 1315 if($colspan > 1) { 1316 $this->_counter['cell_counter'] += $colspan - 1; 1317 $this->doc .= ' colspan="'.$colspan.'"'; 1318 } 1319 if($rowspan > 1) { 1320 $this->doc .= ' rowspan="'.$rowspan.'"'; 1321 } 1322 $this->doc .= '>'; 1323 } 1324 1325 /** 1326 * Close a table header cell 1327 */ 1328 function tableheader_close() { 1329 $this->doc .= '</th>'; 1330 } 1331 1332 /** 1333 * Open a table cell 1334 * 1335 * @param int $colspan 1336 * @param string $align left|center|right 1337 * @param int $rowspan 1338 */ 1339 function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { 1340 $class = 'class="col'.$this->_counter['cell_counter']++; 1341 if(!is_null($align)) { 1342 $class .= ' '.$align.'align'; 1343 } 1344 $class .= '"'; 1345 $this->doc .= '<td '.$class; 1346 if($colspan > 1) { 1347 $this->_counter['cell_counter'] += $colspan - 1; 1348 $this->doc .= ' colspan="'.$colspan.'"'; 1349 } 1350 if($rowspan > 1) { 1351 $this->doc .= ' rowspan="'.$rowspan.'"'; 1352 } 1353 $this->doc .= '>'; 1354 } 1355 1356 /** 1357 * Close a table cell 1358 */ 1359 function tablecell_close() { 1360 $this->doc .= '</td>'; 1361 } 1362 1363 #region Utility functions 1364 1365 /** 1366 * Build a link 1367 * 1368 * Assembles all parts defined in $link returns HTML for the link 1369 * 1370 * @author Andreas Gohr <andi@splitbrain.org> 1371 */ 1372 function _formatLink($link) { 1373 //make sure the url is XHTML compliant (skip mailto) 1374 if(substr($link['url'], 0, 7) != 'mailto:') { 1375 $link['url'] = str_replace('&', '&', $link['url']); 1376 $link['url'] = str_replace('&amp;', '&', $link['url']); 1377 } 1378 //remove double encodings in titles 1379 $link['title'] = str_replace('&amp;', '&', $link['title']); 1380 1381 // be sure there are no bad chars in url or title 1382 // (we can't do this for name because it can contain an img tag) 1383 $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); 1384 $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); 1385 1386 $ret = ''; 1387 $ret .= $link['pre']; 1388 $ret .= '<a href="'.$link['url'].'"'; 1389 if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 1390 if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 1391 if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 1392 if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 1393 if(!empty($link['rel'])) $ret .= ' rel="'.$link['rel'].'"'; 1394 if(!empty($link['more'])) $ret .= ' '.$link['more']; 1395 $ret .= '>'; 1396 $ret .= $link['name']; 1397 $ret .= '</a>'; 1398 $ret .= $link['suf']; 1399 return $ret; 1400 } 1401 1402 /** 1403 * Renders internal and external media 1404 * 1405 * @author Andreas Gohr <andi@splitbrain.org> 1406 * @param string $src media ID 1407 * @param string $title descriptive text 1408 * @param string $align left|center|right 1409 * @param int $width width of media in pixel 1410 * @param int $height height of media in pixel 1411 * @param string $cache cache|recache|nocache 1412 * @param bool $render should the media be embedded inline or just linked 1413 * @return string 1414 */ 1415 function _media($src, $title = null, $align = null, $width = null, 1416 $height = null, $cache = null, $render = true) { 1417 1418 $ret = ''; 1419 1420 list($ext, $mime) = mimetype($src); 1421 if(substr($mime, 0, 5) == 'image') { 1422 // first get the $title 1423 if(!is_null($title)) { 1424 $title = $this->_xmlEntities($title); 1425 } elseif($ext == 'jpg' || $ext == 'jpeg') { 1426 //try to use the caption from IPTC/EXIF 1427 require_once(DOKU_INC.'inc/JpegMeta.php'); 1428 $jpeg = new JpegMeta(mediaFN($src)); 1429 if($jpeg !== false) $cap = $jpeg->getTitle(); 1430 if(!empty($cap)) { 1431 $title = $this->_xmlEntities($cap); 1432 } 1433 } 1434 if(!$render) { 1435 // if the picture is not supposed to be rendered 1436 // return the title of the picture 1437 if(!$title) { 1438 // just show the sourcename 1439 $title = $this->_xmlEntities(utf8_basename(noNS($src))); 1440 } 1441 return $title; 1442 } 1443 //add image tag 1444 $ret .= '<img src="'.ml($src, array('w' => $width, 'h' => $height, 'cache' => $cache, 'rev'=>$this->_getLastMediaRevisionAt($src))).'"'; 1445 $ret .= ' class="media'.$align.'"'; 1446 1447 if($title) { 1448 $ret .= ' title="'.$title.'"'; 1449 $ret .= ' alt="'.$title.'"'; 1450 } else { 1451 $ret .= ' alt=""'; 1452 } 1453 1454 if(!is_null($width)) 1455 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 1456 1457 if(!is_null($height)) 1458 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 1459 1460 $ret .= ' />'; 1461 1462 } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 1463 // first get the $title 1464 $title = !is_null($title) ? $this->_xmlEntities($title) : false; 1465 if(!$render) { 1466 // if the file is not supposed to be rendered 1467 // return the title of the file (just the sourcename if there is no title) 1468 return $title ? $title : $this->_xmlEntities(utf8_basename(noNS($src))); 1469 } 1470 1471 $att = array(); 1472 $att['class'] = "media$align"; 1473 if($title) { 1474 $att['title'] = $title; 1475 } 1476 1477 if(media_supportedav($mime, 'video')) { 1478 //add video 1479 $ret .= $this->_video($src, $width, $height, $att); 1480 } 1481 if(media_supportedav($mime, 'audio')) { 1482 //add audio 1483 $ret .= $this->_audio($src, $att); 1484 } 1485 1486 } elseif($mime == 'application/x-shockwave-flash') { 1487 if(!$render) { 1488 // if the flash is not supposed to be rendered 1489 // return the title of the flash 1490 if(!$title) { 1491 // just show the sourcename 1492 $title = utf8_basename(noNS($src)); 1493 } 1494 return $this->_xmlEntities($title); 1495 } 1496 1497 $att = array(); 1498 $att['class'] = "media$align"; 1499 if($align == 'right') $att['align'] = 'right'; 1500 if($align == 'left') $att['align'] = 'left'; 1501 $ret .= html_flashobject( 1502 ml($src, array('cache' => $cache), true, '&'), $width, $height, 1503 array('quality' => 'high'), 1504 null, 1505 $att, 1506 $this->_xmlEntities($title) 1507 ); 1508 } elseif($title) { 1509 // well at least we have a title to display 1510 $ret .= $this->_xmlEntities($title); 1511 } else { 1512 // just show the sourcename 1513 $ret .= $this->_xmlEntities(utf8_basename(noNS($src))); 1514 } 1515 1516 return $ret; 1517 } 1518 1519 /** 1520 * Escape string for output 1521 * 1522 * @param $string 1523 * @return string 1524 */ 1525 function _xmlEntities($string) { 1526 return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 1527 } 1528 1529 /** 1530 * Creates a linkid from a headline 1531 * 1532 * @author Andreas Gohr <andi@splitbrain.org> 1533 * @param string $title The headline title 1534 * @param boolean $create Create a new unique ID? 1535 * @return string 1536 */ 1537 function _headerToLink($title, $create = false) { 1538 if($create) { 1539 return sectionID($title, $this->headers); 1540 } else { 1541 $check = false; 1542 return sectionID($title, $check); 1543 } 1544 } 1545 1546 /** 1547 * Construct a title and handle images in titles 1548 * 1549 * @author Harry Fuecks <hfuecks@gmail.com> 1550 * @param string|array $title either string title or media array 1551 * @param string $default default title if nothing else is found 1552 * @param bool $isImage will be set to true if it's a media file 1553 * @param null|string $id linked page id (used to extract title from first heading) 1554 * @param string $linktype content|navigation 1555 * @return string HTML of the title, might be full image tag or just escaped text 1556 */ 1557 function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { 1558 $isImage = false; 1559 if(is_array($title)) { 1560 $isImage = true; 1561 return $this->_imageTitle($title); 1562 } elseif(is_null($title) || trim($title) == '') { 1563 if(useHeading($linktype) && $id) { 1564 $heading = p_get_first_heading($id); 1565 if($heading) { 1566 return $this->_xmlEntities($heading); 1567 } 1568 } 1569 return $this->_xmlEntities($default); 1570 } else { 1571 return $this->_xmlEntities($title); 1572 } 1573 } 1574 1575 /** 1576 * Returns HTML code for images used in link titles 1577 * 1578 * @author Andreas Gohr <andi@splitbrain.org> 1579 * @param string $img 1580 * @return string HTML img tag or similar 1581 */ 1582 function _imageTitle($img) { 1583 global $ID; 1584 1585 // some fixes on $img['src'] 1586 // see internalmedia() and externalmedia() 1587 list($img['src']) = explode('#', $img['src'], 2); 1588 if($img['type'] == 'internalmedia') { 1589 resolve_mediaid(getNS($ID), $img['src'], $exists ,$this->date_at, true); 1590 } 1591 1592 return $this->_media( 1593 $img['src'], 1594 $img['title'], 1595 $img['align'], 1596 $img['width'], 1597 $img['height'], 1598 $img['cache'] 1599 ); 1600 } 1601 1602 /** 1603 * helperfunction to return a basic link to a media 1604 * 1605 * used in internalmedia() and externalmedia() 1606 * 1607 * @author Pierre Spring <pierre.spring@liip.ch> 1608 * @param string $src media ID 1609 * @param string $title descriptive text 1610 * @param string $align left|center|right 1611 * @param int $width width of media in pixel 1612 * @param int $height height of media in pixel 1613 * @param string $cache cache|recache|nocache 1614 * @param bool $render should the media be embedded inline or just linked 1615 * @return array associative array with link config 1616 */ 1617 function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { 1618 global $conf; 1619 1620 $link = array(); 1621 $link['class'] = 'media'; 1622 $link['style'] = ''; 1623 $link['pre'] = ''; 1624 $link['suf'] = ''; 1625 $link['more'] = ''; 1626 $link['target'] = $conf['target']['media']; 1627 $link['title'] = $this->_xmlEntities($src); 1628 $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1629 1630 return $link; 1631 } 1632 1633 /** 1634 * Embed video(s) in HTML 1635 * 1636 * @author Anika Henke <anika@selfthinker.org> 1637 * 1638 * @param string $src - ID of video to embed 1639 * @param int $width - width of the video in pixels 1640 * @param int $height - height of the video in pixels 1641 * @param array $atts - additional attributes for the <video> tag 1642 * @return string 1643 */ 1644 function _video($src, $width, $height, $atts = null) { 1645 // prepare width and height 1646 if(is_null($atts)) $atts = array(); 1647 $atts['width'] = (int) $width; 1648 $atts['height'] = (int) $height; 1649 if(!$atts['width']) $atts['width'] = 320; 1650 if(!$atts['height']) $atts['height'] = 240; 1651 1652 $posterUrl = ''; 1653 $files = array(); 1654 $isExternal = media_isexternal($src); 1655 1656 if ($isExternal) { 1657 // take direct source for external files 1658 list(/*ext*/, $srcMime) = mimetype($src); 1659 $files[$srcMime] = $src; 1660 } else { 1661 // prepare alternative formats 1662 $extensions = array('webm', 'ogv', 'mp4'); 1663 $files = media_alternativefiles($src, $extensions); 1664 $poster = media_alternativefiles($src, array('jpg', 'png'), true); 1665 if(!empty($poster)) { 1666 $posterUrl = ml(reset($poster), '', true, '&'); 1667 } 1668 } 1669 1670 $out = ''; 1671 // open video tag 1672 $out .= '<video '.buildAttributes($atts).' controls="controls"'; 1673 if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; 1674 $out .= '>'.NL; 1675 $fallback = ''; 1676 1677 // output source for each alternative video format 1678 foreach($files as $mime => $file) { 1679 if ($isExternal) { 1680 $url = $file; 1681 $linkType = 'externalmedia'; 1682 } else { 1683 $url = ml($file, '', true, '&'); 1684 $linkType = 'internalmedia'; 1685 } 1686 $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); 1687 1688 $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1689 // alternative content (just a link to the file) 1690 $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); 1691 } 1692 1693 // finish 1694 $out .= $fallback; 1695 $out .= '</video>'.NL; 1696 return $out; 1697 } 1698 1699 /** 1700 * Embed audio in HTML 1701 * 1702 * @author Anika Henke <anika@selfthinker.org> 1703 * 1704 * @param string $src - ID of audio to embed 1705 * @param array $atts - additional attributes for the <audio> tag 1706 * @return string 1707 */ 1708 function _audio($src, $atts = null) { 1709 $files = array(); 1710 $isExternal = media_isexternal($src); 1711 1712 if ($isExternal) { 1713 // take direct source for external files 1714 list(/*ext*/, $srcMime) = mimetype($src); 1715 $files[$srcMime] = $src; 1716 } else { 1717 // prepare alternative formats 1718 $extensions = array('ogg', 'mp3', 'wav'); 1719 $files = media_alternativefiles($src, $extensions); 1720 } 1721 1722 $out = ''; 1723 // open audio tag 1724 $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; 1725 $fallback = ''; 1726 1727 // output source for each alternative audio format 1728 foreach($files as $mime => $file) { 1729 if ($isExternal) { 1730 $url = $file; 1731 $linkType = 'externalmedia'; 1732 } else { 1733 $url = ml($file, '', true, '&'); 1734 $linkType = 'internalmedia'; 1735 } 1736 $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); 1737 1738 $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1739 // alternative content (just a link to the file) 1740 $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); 1741 } 1742 1743 // finish 1744 $out .= $fallback; 1745 $out .= '</audio>'.NL; 1746 return $out; 1747 } 1748 1749 /** 1750 * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 1751 * which returns an existing media revision less or equal to rev or date_at 1752 * 1753 * @author lisps 1754 * @param string $media_id 1755 * @access protected 1756 * @return string revision ('' for current) 1757 */ 1758 function _getLastMediaRevisionAt($media_id){ 1759 if(!$this->date_at || media_isexternal($media_id)) return ''; 1760 $pagelog = new MediaChangeLog($media_id); 1761 return $pagelog->getLastRevisionAt($this->date_at); 1762 } 1763 1764 #endregion 1765} 1766 1767//Setup VIM: ex: et ts=4 : 1768