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 string $type The section type identifier 62 * @param string $title The section title 63 * @param int $start The byte position for the edit start 64 * @return string A marker class for the starting HTML element 65 * 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 int $end The byte position for the edit end; null for the rest of the page 77 * 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); 823 824 $link = array(); 825 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 826 if(!$isImage) { 827 if($exists) { 828 $class = 'wikilink1'; 829 } else { 830 $class = 'wikilink2'; 831 $link['rel'] = 'nofollow'; 832 } 833 } else { 834 $class = 'media'; 835 } 836 837 //keep hash anchor 838 @list($id, $hash) = explode('#', $id, 2); 839 if(!empty($hash)) $hash = $this->_headerToLink($hash); 840 841 //prepare for formating 842 $link['target'] = $conf['target']['wiki']; 843 $link['style'] = ''; 844 $link['pre'] = ''; 845 $link['suf'] = ''; 846 // highlight link to current page 847 if($id == $INFO['id']) { 848 $link['pre'] = '<span class="curid">'; 849 $link['suf'] = '</span>'; 850 } 851 $link['more'] = ''; 852 $link['class'] = $class; 853 $link['url'] = wl($id, $params); 854 $link['name'] = $name; 855 $link['title'] = $id; 856 //add search string 857 if($search) { 858 ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 859 if(is_array($search)) { 860 $search = array_map('rawurlencode', $search); 861 $link['url'] .= 's[]='.join('&s[]=', $search); 862 } else { 863 $link['url'] .= 's='.rawurlencode($search); 864 } 865 } 866 867 //keep hash 868 if($hash) $link['url'] .= '#'.$hash; 869 870 //output formatted 871 if($returnonly) { 872 return $this->_formatLink($link); 873 } else { 874 $this->doc .= $this->_formatLink($link); 875 } 876 } 877 878 /** 879 * Render an external link 880 * 881 * @param string $url full URL with scheme 882 * @param string|array $name name for the link, array for media file 883 */ 884 function externallink($url, $name = null) { 885 global $conf; 886 887 $name = $this->_getLinkTitle($name, $url, $isImage); 888 889 // url might be an attack vector, only allow registered protocols 890 if(is_null($this->schemes)) $this->schemes = getSchemes(); 891 list($scheme) = explode('://', $url); 892 $scheme = strtolower($scheme); 893 if(!in_array($scheme, $this->schemes)) $url = ''; 894 895 // is there still an URL? 896 if(!$url) { 897 $this->doc .= $name; 898 return; 899 } 900 901 // set class 902 if(!$isImage) { 903 $class = 'urlextern'; 904 } else { 905 $class = 'media'; 906 } 907 908 //prepare for formating 909 $link = array(); 910 $link['target'] = $conf['target']['extern']; 911 $link['style'] = ''; 912 $link['pre'] = ''; 913 $link['suf'] = ''; 914 $link['more'] = ''; 915 $link['class'] = $class; 916 $link['url'] = $url; 917 918 $link['name'] = $name; 919 $link['title'] = $this->_xmlEntities($url); 920 if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 921 922 //output formatted 923 $this->doc .= $this->_formatLink($link); 924 } 925 926 /** 927 * Render an interwiki link 928 * 929 * You may want to use $this->_resolveInterWiki() here 930 * 931 * @param string $match original link - probably not much use 932 * @param string|array $name name for the link, array for media file 933 * @param string $wikiName indentifier (shortcut) for the remote wiki 934 * @param string $wikiUri the fragment parsed from the original link 935 */ 936 function interwikilink($match, $name = null, $wikiName, $wikiUri) { 937 global $conf; 938 939 $link = array(); 940 $link['target'] = $conf['target']['interwiki']; 941 $link['pre'] = ''; 942 $link['suf'] = ''; 943 $link['more'] = ''; 944 $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 945 946 //get interwiki URL 947 $exists = null; 948 $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 949 950 if(!$isImage) { 951 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 952 $link['class'] = "interwiki iw_$class"; 953 } else { 954 $link['class'] = 'media'; 955 } 956 957 //do we stay at the same server? Use local target 958 if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 959 $link['target'] = $conf['target']['wiki']; 960 } 961 if($exists !== null && !$isImage) { 962 if($exists) { 963 $link['class'] .= ' wikilink1'; 964 } else { 965 $link['class'] .= ' wikilink2'; 966 $link['rel'] = 'nofollow'; 967 } 968 } 969 970 $link['url'] = $url; 971 $link['title'] = htmlspecialchars($link['url']); 972 973 //output formatted 974 $this->doc .= $this->_formatLink($link); 975 } 976 977 /** 978 * Link to windows share 979 * 980 * @param string $url the link 981 * @param string|array $name name for the link, array for media file 982 */ 983 function windowssharelink($url, $name = null) { 984 global $conf; 985 986 //simple setup 987 $link = array(); 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 = 'file:///'.$url; 1003 $link['url'] = $url; 1004 1005 //output formatted 1006 $this->doc .= $this->_formatLink($link); 1007 } 1008 1009 /** 1010 * Render a linked E-Mail Address 1011 * 1012 * Honors $conf['mailguard'] setting 1013 * 1014 * @param string $address Email-Address 1015 * @param string|array $name name for the link, array for media file 1016 */ 1017 function emaillink($address, $name = null) { 1018 global $conf; 1019 //simple setup 1020 $link = array(); 1021 $link['target'] = ''; 1022 $link['pre'] = ''; 1023 $link['suf'] = ''; 1024 $link['style'] = ''; 1025 $link['more'] = ''; 1026 1027 $name = $this->_getLinkTitle($name, '', $isImage); 1028 if(!$isImage) { 1029 $link['class'] = 'mail'; 1030 } else { 1031 $link['class'] = 'media'; 1032 } 1033 1034 $address = $this->_xmlEntities($address); 1035 $address = obfuscate($address); 1036 $title = $address; 1037 1038 if(empty($name)) { 1039 $name = $address; 1040 } 1041 1042 if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1043 1044 $link['url'] = 'mailto:'.$address; 1045 $link['name'] = $name; 1046 $link['title'] = $title; 1047 1048 //output formatted 1049 $this->doc .= $this->_formatLink($link); 1050 } 1051 1052 /** 1053 * Render an internal media file 1054 * 1055 * @param string $src media ID 1056 * @param string $title descriptive text 1057 * @param string $align left|center|right 1058 * @param int $width width of media in pixel 1059 * @param int $height height of media in pixel 1060 * @param string $cache cache|recache|nocache 1061 * @param string $linking linkonly|detail|nolink 1062 * @param bool $return return HTML instead of adding to $doc 1063 * @return void|string 1064 */ 1065 function internalmedia($src, $title = null, $align = null, $width = null, 1066 $height = null, $cache = null, $linking = null, $return = false) { 1067 global $ID; 1068 list($src, $hash) = explode('#', $src, 2); 1069 resolve_mediaid(getNS($ID), $src, $exists); 1070 1071 $noLink = false; 1072 $render = ($linking == 'linkonly') ? false : true; 1073 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1074 1075 list($ext, $mime) = mimetype($src, false); 1076 if(substr($mime, 0, 5) == 'image' && $render) { 1077 $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache), ($linking == 'direct')); 1078 } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 1079 // don't link movies 1080 $noLink = true; 1081 } else { 1082 // add file icons 1083 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 1084 $link['class'] .= ' mediafile mf_'.$class; 1085 $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache), true); 1086 if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; 1087 } 1088 1089 if($hash) $link['url'] .= '#'.$hash; 1090 1091 //markup non existing files 1092 if(!$exists) { 1093 $link['class'] .= ' wikilink2'; 1094 } 1095 1096 //output formatted 1097 if($return) { 1098 if($linking == 'nolink' || $noLink) return $link['name']; 1099 else return $this->_formatLink($link); 1100 } else { 1101 if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 1102 else $this->doc .= $this->_formatLink($link); 1103 } 1104 } 1105 1106 /** 1107 * Render an external media file 1108 * 1109 * @param string $src full media URL 1110 * @param string $title descriptive text 1111 * @param string $align left|center|right 1112 * @param int $width width of media in pixel 1113 * @param int $height height of media in pixel 1114 * @param string $cache cache|recache|nocache 1115 * @param string $linking linkonly|detail|nolink 1116 * @param bool $return return HTML instead of adding to $doc 1117 */ 1118 function externalmedia($src, $title = null, $align = null, $width = null, 1119 $height = null, $cache = null, $linking = null, $return = false) { 1120 list($src, $hash) = explode('#', $src, 2); 1121 $noLink = false; 1122 $render = ($linking == 'linkonly') ? false : true; 1123 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1124 1125 $link['url'] = ml($src, array('cache' => $cache)); 1126 1127 list($ext, $mime) = mimetype($src, false); 1128 if(substr($mime, 0, 5) == 'image' && $render) { 1129 // link only jpeg images 1130 // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1131 } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 1132 // don't link movies 1133 $noLink = true; 1134 } else { 1135 // add file icons 1136 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 1137 $link['class'] .= ' mediafile mf_'.$class; 1138 } 1139 1140 if($hash) $link['url'] .= '#'.$hash; 1141 1142 //output formatted 1143 if($return) { 1144 if($linking == 'nolink' || $noLink) return $link['name']; 1145 else return $this->_formatLink($link); 1146 } else { 1147 if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 1148 else $this->doc .= $this->_formatLink($link); 1149 } 1150 } 1151 1152 /** 1153 * Renders an RSS feed 1154 * 1155 * @author Andreas Gohr <andi@splitbrain.org> 1156 */ 1157 function rss($url, $params) { 1158 global $lang; 1159 global $conf; 1160 1161 require_once(DOKU_INC.'inc/FeedParser.php'); 1162 $feed = new FeedParser(); 1163 $feed->set_feed_url($url); 1164 1165 //disable warning while fetching 1166 if(!defined('DOKU_E_LEVEL')) { 1167 $elvl = error_reporting(E_ERROR); 1168 } 1169 $rc = $feed->init(); 1170 if(isset($elvl)) { 1171 error_reporting($elvl); 1172 } 1173 1174 //decide on start and end 1175 if($params['reverse']) { 1176 $mod = -1; 1177 $start = $feed->get_item_quantity() - 1; 1178 $end = $start - ($params['max']); 1179 $end = ($end < -1) ? -1 : $end; 1180 } else { 1181 $mod = 1; 1182 $start = 0; 1183 $end = $feed->get_item_quantity(); 1184 $end = ($end > $params['max']) ? $params['max'] : $end; 1185 } 1186 1187 $this->doc .= '<ul class="rss">'; 1188 if($rc) { 1189 for($x = $start; $x != $end; $x += $mod) { 1190 $item = $feed->get_item($x); 1191 $this->doc .= '<li><div class="li">'; 1192 // support feeds without links 1193 $lnkurl = $item->get_permalink(); 1194 if($lnkurl) { 1195 // title is escaped by SimplePie, we unescape here because it 1196 // is escaped again in externallink() FS#1705 1197 $this->externallink( 1198 $item->get_permalink(), 1199 html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') 1200 ); 1201 } else { 1202 $this->doc .= ' '.$item->get_title(); 1203 } 1204 if($params['author']) { 1205 $author = $item->get_author(0); 1206 if($author) { 1207 $name = $author->get_name(); 1208 if(!$name) $name = $author->get_email(); 1209 if($name) $this->doc .= ' '.$lang['by'].' '.$name; 1210 } 1211 } 1212 if($params['date']) { 1213 $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 1214 } 1215 if($params['details']) { 1216 $this->doc .= '<div class="detail">'; 1217 if($conf['htmlok']) { 1218 $this->doc .= $item->get_description(); 1219 } else { 1220 $this->doc .= strip_tags($item->get_description()); 1221 } 1222 $this->doc .= '</div>'; 1223 } 1224 1225 $this->doc .= '</div></li>'; 1226 } 1227 } else { 1228 $this->doc .= '<li><div class="li">'; 1229 $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 1230 $this->externallink($url); 1231 if($conf['allowdebug']) { 1232 $this->doc .= '<!--'.hsc($feed->error).'-->'; 1233 } 1234 $this->doc .= '</div></li>'; 1235 } 1236 $this->doc .= '</ul>'; 1237 } 1238 1239 /** 1240 * Start a table 1241 * 1242 * @param int $maxcols maximum number of columns 1243 * @param int $numrows NOT IMPLEMENTED 1244 * @param int $pos byte position in the original source 1245 */ 1246 function table_open($maxcols = null, $numrows = null, $pos = null) { 1247 // initialize the row counter used for classes 1248 $this->_counter['row_counter'] = 0; 1249 $class = 'table'; 1250 if($pos !== null) { 1251 $class .= ' '.$this->startSectionEdit($pos, 'table'); 1252 } 1253 $this->doc .= '<div class="'.$class.'"><table class="inline">'. 1254 DOKU_LF; 1255 } 1256 1257 /** 1258 * Close a table 1259 * 1260 * @param int $pos byte position in the original source 1261 */ 1262 function table_close($pos = null) { 1263 $this->doc .= '</table></div>'.DOKU_LF; 1264 if($pos !== null) { 1265 $this->finishSectionEdit($pos); 1266 } 1267 } 1268 1269 /** 1270 * Open a table header 1271 */ 1272 function tablethead_open() { 1273 $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 1274 } 1275 1276 /** 1277 * Close a table header 1278 */ 1279 function tablethead_close() { 1280 $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 1281 } 1282 1283 /** 1284 * Open a table row 1285 */ 1286 function tablerow_open() { 1287 // initialize the cell counter used for classes 1288 $this->_counter['cell_counter'] = 0; 1289 $class = 'row'.$this->_counter['row_counter']++; 1290 $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; 1291 } 1292 1293 /** 1294 * Close a table row 1295 */ 1296 function tablerow_close() { 1297 $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 1298 } 1299 1300 /** 1301 * Open a table header cell 1302 * 1303 * @param int $colspan 1304 * @param string $align left|center|right 1305 * @param int $rowspan 1306 */ 1307 function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { 1308 $class = 'class="col'.$this->_counter['cell_counter']++; 1309 if(!is_null($align)) { 1310 $class .= ' '.$align.'align'; 1311 } 1312 $class .= '"'; 1313 $this->doc .= '<th '.$class; 1314 if($colspan > 1) { 1315 $this->_counter['cell_counter'] += $colspan - 1; 1316 $this->doc .= ' colspan="'.$colspan.'"'; 1317 } 1318 if($rowspan > 1) { 1319 $this->doc .= ' rowspan="'.$rowspan.'"'; 1320 } 1321 $this->doc .= '>'; 1322 } 1323 1324 /** 1325 * Close a table header cell 1326 */ 1327 function tableheader_close() { 1328 $this->doc .= '</th>'; 1329 } 1330 1331 /** 1332 * Open a table cell 1333 * 1334 * @param int $colspan 1335 * @param string $align left|center|right 1336 * @param int $rowspan 1337 */ 1338 function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { 1339 $class = 'class="col'.$this->_counter['cell_counter']++; 1340 if(!is_null($align)) { 1341 $class .= ' '.$align.'align'; 1342 } 1343 $class .= '"'; 1344 $this->doc .= '<td '.$class; 1345 if($colspan > 1) { 1346 $this->_counter['cell_counter'] += $colspan - 1; 1347 $this->doc .= ' colspan="'.$colspan.'"'; 1348 } 1349 if($rowspan > 1) { 1350 $this->doc .= ' rowspan="'.$rowspan.'"'; 1351 } 1352 $this->doc .= '>'; 1353 } 1354 1355 /** 1356 * Close a table cell 1357 */ 1358 function tablecell_close() { 1359 $this->doc .= '</td>'; 1360 } 1361 1362 #region Utility functions 1363 1364 /** 1365 * Build a link 1366 * 1367 * Assembles all parts defined in $link returns HTML for the link 1368 * 1369 * @author Andreas Gohr <andi@splitbrain.org> 1370 */ 1371 function _formatLink($link) { 1372 //make sure the url is XHTML compliant (skip mailto) 1373 if(substr($link['url'], 0, 7) != 'mailto:') { 1374 $link['url'] = str_replace('&', '&', $link['url']); 1375 $link['url'] = str_replace('&amp;', '&', $link['url']); 1376 } 1377 //remove double encodings in titles 1378 $link['title'] = str_replace('&amp;', '&', $link['title']); 1379 1380 // be sure there are no bad chars in url or title 1381 // (we can't do this for name because it can contain an img tag) 1382 $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); 1383 $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); 1384 1385 $ret = ''; 1386 $ret .= $link['pre']; 1387 $ret .= '<a href="'.$link['url'].'"'; 1388 if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 1389 if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 1390 if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 1391 if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 1392 if(!empty($link['rel'])) $ret .= ' rel="'.$link['rel'].'"'; 1393 if(!empty($link['more'])) $ret .= ' '.$link['more']; 1394 $ret .= '>'; 1395 $ret .= $link['name']; 1396 $ret .= '</a>'; 1397 $ret .= $link['suf']; 1398 return $ret; 1399 } 1400 1401 /** 1402 * Renders internal and external media 1403 * 1404 * @author Andreas Gohr <andi@splitbrain.org> 1405 * @param string $src media ID 1406 * @param string $title descriptive text 1407 * @param string $align left|center|right 1408 * @param int $width width of media in pixel 1409 * @param int $height height of media in pixel 1410 * @param string $cache cache|recache|nocache 1411 * @param bool $render should the media be embedded inline or just linked 1412 * @return string 1413 */ 1414 function _media($src, $title = null, $align = null, $width = null, 1415 $height = null, $cache = null, $render = true) { 1416 1417 $ret = ''; 1418 1419 list($ext, $mime) = mimetype($src); 1420 if(substr($mime, 0, 5) == 'image') { 1421 // first get the $title 1422 if(!is_null($title)) { 1423 $title = $this->_xmlEntities($title); 1424 } elseif($ext == 'jpg' || $ext == 'jpeg') { 1425 //try to use the caption from IPTC/EXIF 1426 require_once(DOKU_INC.'inc/JpegMeta.php'); 1427 $jpeg = new JpegMeta(mediaFN($src)); 1428 if($jpeg !== false) $cap = $jpeg->getTitle(); 1429 if(!empty($cap)) { 1430 $title = $this->_xmlEntities($cap); 1431 } 1432 } 1433 if(!$render) { 1434 // if the picture is not supposed to be rendered 1435 // return the title of the picture 1436 if(!$title) { 1437 // just show the sourcename 1438 $title = $this->_xmlEntities(utf8_basename(noNS($src))); 1439 } 1440 return $title; 1441 } 1442 //add image tag 1443 $ret .= '<img src="'.ml($src, array('w' => $width, 'h' => $height, 'cache' => $cache)).'"'; 1444 $ret .= ' class="media'.$align.'"'; 1445 1446 if($title) { 1447 $ret .= ' title="'.$title.'"'; 1448 $ret .= ' alt="'.$title.'"'; 1449 } else { 1450 $ret .= ' alt=""'; 1451 } 1452 1453 if(!is_null($width)) 1454 $ret .= ' width="'.$this->_xmlEntities($width).'"'; 1455 1456 if(!is_null($height)) 1457 $ret .= ' height="'.$this->_xmlEntities($height).'"'; 1458 1459 $ret .= ' />'; 1460 1461 } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 1462 // first get the $title 1463 $title = !is_null($title) ? $this->_xmlEntities($title) : false; 1464 if(!$render) { 1465 // if the file is not supposed to be rendered 1466 // return the title of the file (just the sourcename if there is no title) 1467 return $title ? $title : $this->_xmlEntities(utf8_basename(noNS($src))); 1468 } 1469 1470 $att = array(); 1471 $att['class'] = "media$align"; 1472 if($title) { 1473 $att['title'] = $title; 1474 } 1475 1476 if(media_supportedav($mime, 'video')) { 1477 //add video 1478 $ret .= $this->_video($src, $width, $height, $att); 1479 } 1480 if(media_supportedav($mime, 'audio')) { 1481 //add audio 1482 $ret .= $this->_audio($src, $att); 1483 } 1484 1485 } elseif($mime == 'application/x-shockwave-flash') { 1486 if(!$render) { 1487 // if the flash is not supposed to be rendered 1488 // return the title of the flash 1489 if(!$title) { 1490 // just show the sourcename 1491 $title = utf8_basename(noNS($src)); 1492 } 1493 return $this->_xmlEntities($title); 1494 } 1495 1496 $att = array(); 1497 $att['class'] = "media$align"; 1498 if($align == 'right') $att['align'] = 'right'; 1499 if($align == 'left') $att['align'] = 'left'; 1500 $ret .= html_flashobject( 1501 ml($src, array('cache' => $cache), true, '&'), $width, $height, 1502 array('quality' => 'high'), 1503 null, 1504 $att, 1505 $this->_xmlEntities($title) 1506 ); 1507 } elseif($title) { 1508 // well at least we have a title to display 1509 $ret .= $this->_xmlEntities($title); 1510 } else { 1511 // just show the sourcename 1512 $ret .= $this->_xmlEntities(utf8_basename(noNS($src))); 1513 } 1514 1515 return $ret; 1516 } 1517 1518 /** 1519 * Escape string for output 1520 * 1521 * @param $string 1522 * @return string 1523 */ 1524 function _xmlEntities($string) { 1525 return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 1526 } 1527 1528 /** 1529 * Creates a linkid from a headline 1530 * 1531 * @author Andreas Gohr <andi@splitbrain.org> 1532 * @param string $title The headline title 1533 * @param boolean $create Create a new unique ID? 1534 * @return string 1535 */ 1536 function _headerToLink($title, $create = false) { 1537 if($create) { 1538 return sectionID($title, $this->headers); 1539 } else { 1540 $check = false; 1541 return sectionID($title, $check); 1542 } 1543 } 1544 1545 /** 1546 * Construct a title and handle images in titles 1547 * 1548 * @author Harry Fuecks <hfuecks@gmail.com> 1549 * @param string|array $title either string title or media array 1550 * @param string $default default title if nothing else is found 1551 * @param bool $isImage will be set to true if it's a media file 1552 * @param null|string $id linked page id (used to extract title from first heading) 1553 * @param string $linktype content|navigation 1554 * @return string HTML of the title, might be full image tag or just escaped text 1555 */ 1556 function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { 1557 $isImage = false; 1558 if(is_array($title)) { 1559 $isImage = true; 1560 return $this->_imageTitle($title); 1561 } elseif(is_null($title) || trim($title) == '') { 1562 if(useHeading($linktype) && $id) { 1563 $heading = p_get_first_heading($id); 1564 if($heading) { 1565 return $this->_xmlEntities($heading); 1566 } 1567 } 1568 return $this->_xmlEntities($default); 1569 } else { 1570 return $this->_xmlEntities($title); 1571 } 1572 } 1573 1574 /** 1575 * Returns HTML code for images used in link titles 1576 * 1577 * @author Andreas Gohr <andi@splitbrain.org> 1578 * @param array $img 1579 * @return string HTML img tag or similar 1580 */ 1581 function _imageTitle($img) { 1582 global $ID; 1583 1584 // some fixes on $img['src'] 1585 // see internalmedia() and externalmedia() 1586 list($img['src']) = explode('#', $img['src'], 2); 1587 if($img['type'] == 'internalmedia') { 1588 resolve_mediaid(getNS($ID), $img['src'], $exists); 1589 } 1590 1591 return $this->_media( 1592 $img['src'], 1593 $img['title'], 1594 $img['align'], 1595 $img['width'], 1596 $img['height'], 1597 $img['cache'] 1598 ); 1599 } 1600 1601 /** 1602 * helperfunction to return a basic link to a media 1603 * 1604 * used in internalmedia() and externalmedia() 1605 * 1606 * @author Pierre Spring <pierre.spring@liip.ch> 1607 * @param string $src media ID 1608 * @param string $title descriptive text 1609 * @param string $align left|center|right 1610 * @param int $width width of media in pixel 1611 * @param int $height height of media in pixel 1612 * @param string $cache cache|recache|nocache 1613 * @param bool $render should the media be embedded inline or just linked 1614 * @return array associative array with link config 1615 */ 1616 function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { 1617 global $conf; 1618 1619 $link = array(); 1620 $link['class'] = 'media'; 1621 $link['style'] = ''; 1622 $link['pre'] = ''; 1623 $link['suf'] = ''; 1624 $link['more'] = ''; 1625 $link['target'] = $conf['target']['media']; 1626 $link['title'] = $this->_xmlEntities($src); 1627 $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1628 1629 return $link; 1630 } 1631 1632 /** 1633 * Embed video(s) in HTML 1634 * 1635 * @author Anika Henke <anika@selfthinker.org> 1636 * 1637 * @param string $src - ID of video to embed 1638 * @param int $width - width of the video in pixels 1639 * @param int $height - height of the video in pixels 1640 * @param array $atts - additional attributes for the <video> tag 1641 * @return string 1642 */ 1643 function _video($src, $width, $height, $atts = null) { 1644 // prepare width and height 1645 if(is_null($atts)) $atts = array(); 1646 $atts['width'] = (int) $width; 1647 $atts['height'] = (int) $height; 1648 if(!$atts['width']) $atts['width'] = 320; 1649 if(!$atts['height']) $atts['height'] = 240; 1650 1651 $posterUrl = ''; 1652 $files = array(); 1653 $isExternal = media_isexternal($src); 1654 1655 if ($isExternal) { 1656 // take direct source for external files 1657 list(/*ext*/, $srcMime) = mimetype($src); 1658 $files[$srcMime] = $src; 1659 } else { 1660 // prepare alternative formats 1661 $extensions = array('webm', 'ogv', 'mp4'); 1662 $files = media_alternativefiles($src, $extensions); 1663 $poster = media_alternativefiles($src, array('jpg', 'png')); 1664 if(!empty($poster)) { 1665 $posterUrl = ml(reset($poster), '', true, '&'); 1666 } 1667 } 1668 1669 $out = ''; 1670 // open video tag 1671 $out .= '<video '.buildAttributes($atts).' controls="controls"'; 1672 if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; 1673 $out .= '>'.NL; 1674 $fallback = ''; 1675 1676 // output source for each alternative video format 1677 foreach($files as $mime => $file) { 1678 if ($isExternal) { 1679 $url = $file; 1680 $linkType = 'externalmedia'; 1681 } else { 1682 $url = ml($file, '', true, '&'); 1683 $linkType = 'internalmedia'; 1684 } 1685 $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); 1686 1687 $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1688 // alternative content (just a link to the file) 1689 $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); 1690 } 1691 1692 // finish 1693 $out .= $fallback; 1694 $out .= '</video>'.NL; 1695 return $out; 1696 } 1697 1698 /** 1699 * Embed audio in HTML 1700 * 1701 * @author Anika Henke <anika@selfthinker.org> 1702 * 1703 * @param string $src - ID of audio to embed 1704 * @param array $atts - additional attributes for the <audio> tag 1705 * @return string 1706 */ 1707 function _audio($src, $atts = array()) { 1708 $files = array(); 1709 $isExternal = media_isexternal($src); 1710 1711 if ($isExternal) { 1712 // take direct source for external files 1713 list(/*ext*/, $srcMime) = mimetype($src); 1714 $files[$srcMime] = $src; 1715 } else { 1716 // prepare alternative formats 1717 $extensions = array('ogg', 'mp3', 'wav'); 1718 $files = media_alternativefiles($src, $extensions); 1719 } 1720 1721 $out = ''; 1722 // open audio tag 1723 $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; 1724 $fallback = ''; 1725 1726 // output source for each alternative audio format 1727 foreach($files as $mime => $file) { 1728 if ($isExternal) { 1729 $url = $file; 1730 $linkType = 'externalmedia'; 1731 } else { 1732 $url = ml($file, '', true, '&'); 1733 $linkType = 'internalmedia'; 1734 } 1735 $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); 1736 1737 $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1738 // alternative content (just a link to the file) 1739 $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); 1740 } 1741 1742 // finish 1743 $out .= $fallback; 1744 $out .= '</audio>'.NL; 1745 return $out; 1746 } 1747 1748 #endregion 1749} 1750 1751//Setup VIM: ex: et ts=4 : 1752