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