10cecf9d5Sandi<?php 20c3a5702SAndreas Gohr 30c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 42cd6cc0aSAndreas Gohruse dokuwiki\File\MediaResolver; 52cd6cc0aSAndreas Gohruse dokuwiki\File\PageResolver; 60c3a5702SAndreas Gohr 7b625487dSandi/** 8b625487dSandi * Renderer for XHTML output 9b625487dSandi * 10b4f2363aSAndreas Gohr * This is DokuWiki's main renderer used to display page content in the wiki 11b4f2363aSAndreas Gohr * 12b625487dSandi * @author Harry Fuecks <hfuecks@gmail.com> 13b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 143dd5c225SAndreas Gohr * 150cecf9d5Sandi */ 16ac83b9d8Sandiclass Doku_Renderer_xhtml extends Doku_Renderer { 173dd5c225SAndreas Gohr /** @var array store the table of contents */ 183dd5c225SAndreas Gohr public $toc = array(); 190cecf9d5Sandi 203dd5c225SAndreas Gohr /** @var array A stack of section edit data */ 213dd5c225SAndreas Gohr protected $sectionedits = array(); 22de369923SAndreas Gohr 23de369923SAndreas Gohr /** @var string|int link pages and media against this revision */ 24de369923SAndreas Gohr public $date_at = ''; 25c5a8fd96SAndreas Gohr 263dd5c225SAndreas Gohr /** @var int last section edit id, used by startSectionEdit */ 273dd5c225SAndreas Gohr protected $lastsecid = 0; 280cecf9d5Sandi 2916ec3e37SAndreas Gohr /** @var array a list of footnotes, list starts at 1! */ 303dd5c225SAndreas Gohr protected $footnotes = array(); 317764a90aSandi 323dd5c225SAndreas Gohr /** @var int current section level */ 333dd5c225SAndreas Gohr protected $lastlevel = 0; 343dd5c225SAndreas Gohr /** @var array section node tracker */ 353dd5c225SAndreas Gohr protected $node = array(0, 0, 0, 0, 0); 363dd5c225SAndreas Gohr 373dd5c225SAndreas Gohr /** @var string temporary $doc store */ 383dd5c225SAndreas Gohr protected $store = ''; 393dd5c225SAndreas Gohr 403dd5c225SAndreas Gohr /** @var array global counter, for table classes etc. */ 413dd5c225SAndreas Gohr protected $_counter = array(); // 423dd5c225SAndreas Gohr 433dd5c225SAndreas Gohr /** @var int counts the code and file blocks, used to provide download links */ 443dd5c225SAndreas Gohr protected $_codeblock = 0; 453dd5c225SAndreas Gohr 463dd5c225SAndreas Gohr /** @var array list of allowed URL schemes */ 473dd5c225SAndreas Gohr protected $schemes = null; 48b5742cedSPierre Spring 4990df9a4dSAdrian Lang /** 5090df9a4dSAdrian Lang * Register a new edit section range 5190df9a4dSAdrian Lang * 5242ea7f44SGerrit Uitslag * @param int $start The byte position for the edit start 53ec57f119SLarsDW223 * @param array $data Associative array with section data: 54ec57f119SLarsDW223 * Key 'name': the section name/title 55ec57f119SLarsDW223 * Key 'target': the target for the section edit, 56ec57f119SLarsDW223 * e.g. 'section' or 'table' 57ec57f119SLarsDW223 * Key 'hid': header id 58ec57f119SLarsDW223 * Key 'codeblockOffset': actual code block index 59ec57f119SLarsDW223 * Key 'start': set in startSectionEdit(), 60ec57f119SLarsDW223 * do not set yourself 61ec57f119SLarsDW223 * Key 'range': calculated from 'start' and 62ec57f119SLarsDW223 * $key in finishSectionEdit(), 63ec57f119SLarsDW223 * do not set yourself 6490df9a4dSAdrian Lang * @return string A marker class for the starting HTML element 6542ea7f44SGerrit Uitslag * 6690df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 6790df9a4dSAdrian Lang */ 68ec57f119SLarsDW223 public function startSectionEdit($start, $data) { 69ec57f119SLarsDW223 if (!is_array($data)) { 70ac025fdfSAndreas Gohr msg( 71ac025fdfSAndreas Gohr sprintf( 72ac025fdfSAndreas Gohr 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', 73ac025fdfSAndreas Gohr hsc((string) $data) 74ac025fdfSAndreas Gohr ), -1 75ac025fdfSAndreas Gohr ); 76ac025fdfSAndreas Gohr 77ac025fdfSAndreas Gohr // @deprecated 2018-04-14, backward compatibility 78ac025fdfSAndreas Gohr $args = func_get_args(); 79ac025fdfSAndreas Gohr $data = array(); 80ac025fdfSAndreas Gohr if(isset($args[1])) $data['target'] = $args[1]; 81ac025fdfSAndreas Gohr if(isset($args[2])) $data['name'] = $args[2]; 82ac025fdfSAndreas Gohr if(isset($args[3])) $data['hid'] = $args[3]; 83ec57f119SLarsDW223 } 84ec57f119SLarsDW223 $data['secid'] = ++$this->lastsecid; 85ec57f119SLarsDW223 $data['start'] = $start; 86ec57f119SLarsDW223 $this->sectionedits[] = $data; 87ec57f119SLarsDW223 return 'sectionedit'.$data['secid']; 8890df9a4dSAdrian Lang } 8990df9a4dSAdrian Lang 9090df9a4dSAdrian Lang /** 9190df9a4dSAdrian Lang * Finish an edit section range 9290df9a4dSAdrian Lang * 9342ea7f44SGerrit Uitslag * @param int $end The byte position for the edit end; null for the rest of the page 9442ea7f44SGerrit Uitslag * 9590df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 9690df9a4dSAdrian Lang */ 972571786cSLarsDW223 public function finishSectionEdit($end = null, $hid = null) { 98ec57f119SLarsDW223 $data = array_pop($this->sectionedits); 99ec57f119SLarsDW223 if(!is_null($end) && $end <= $data['start']) { 10000c13053SAdrian Lang return; 10100c13053SAdrian Lang } 1022571786cSLarsDW223 if(!is_null($hid)) { 103ec57f119SLarsDW223 $data['hid'] .= $hid; 1042571786cSLarsDW223 } 105ec57f119SLarsDW223 $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end); 106ec57f119SLarsDW223 unset($data['start']); 107ada0d779SMichael Hamann $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->'; 10890df9a4dSAdrian Lang } 10990df9a4dSAdrian Lang 1103dd5c225SAndreas Gohr /** 1113dd5c225SAndreas Gohr * Returns the format produced by this renderer. 1123dd5c225SAndreas Gohr * 1133dd5c225SAndreas Gohr * @return string always 'xhtml' 1143dd5c225SAndreas Gohr */ 115de369923SAndreas Gohr public function getFormat() { 1165f70445dSAndreas Gohr return 'xhtml'; 1175f70445dSAndreas Gohr } 1185f70445dSAndreas Gohr 1193dd5c225SAndreas Gohr /** 1203dd5c225SAndreas Gohr * Initialize the document 1213dd5c225SAndreas Gohr */ 122de369923SAndreas Gohr public function document_start() { 123c5a8fd96SAndreas Gohr //reset some internals 124c5a8fd96SAndreas Gohr $this->toc = array(); 1250cecf9d5Sandi } 1260cecf9d5Sandi 1273dd5c225SAndreas Gohr /** 1283dd5c225SAndreas Gohr * Finalize the document 1293dd5c225SAndreas Gohr */ 130de369923SAndreas Gohr public function document_end() { 13190df9a4dSAdrian Lang // Finish open section edits. 13290df9a4dSAdrian Lang while(count($this->sectionedits) > 0) { 133ec57f119SLarsDW223 if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { 13490df9a4dSAdrian Lang // If there is only one section, do not write a section edit 13590df9a4dSAdrian Lang // marker. 13690df9a4dSAdrian Lang array_pop($this->sectionedits); 13790df9a4dSAdrian Lang } else { 138d9e36cbeSAdrian Lang $this->finishSectionEdit(); 13990df9a4dSAdrian Lang } 14090df9a4dSAdrian Lang } 14190df9a4dSAdrian Lang 1420cecf9d5Sandi if(count($this->footnotes) > 0) { 143a2d649c4Sandi $this->doc .= '<div class="footnotes">'.DOKU_LF; 144d74aace9Schris 14516ec3e37SAndreas Gohr foreach($this->footnotes as $id => $footnote) { 146d74aace9Schris // check its not a placeholder that indicates actual footnote text is elsewhere 147d74aace9Schris if(substr($footnote, 0, 5) != "@@FNT") { 148d74aace9Schris 149d74aace9Schris // open the footnote and set the anchor and backlink 150d74aace9Schris $this->doc .= '<div class="fn">'; 15116cc7ed7SAnika Henke $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">'; 15229bfcd16SAndreas Gohr $this->doc .= $id.')</a></sup> '.DOKU_LF; 153d74aace9Schris 154d74aace9Schris // get any other footnotes that use the same markup 155d74aace9Schris $alt = array_keys($this->footnotes, "@@FNT$id"); 156d74aace9Schris 157d74aace9Schris if(count($alt)) { 158d74aace9Schris foreach($alt as $ref) { 159d74aace9Schris // set anchor and backlink for the other footnotes 16016ec3e37SAndreas Gohr $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">'; 16116ec3e37SAndreas Gohr $this->doc .= ($ref).')</a></sup> '.DOKU_LF; 162d74aace9Schris } 163d74aace9Schris } 164d74aace9Schris 165d74aace9Schris // add footnote markup and close this footnote 166694afa06SAnika Henke $this->doc .= '<div class="content">'.$footnote.'</div>'; 167d74aace9Schris $this->doc .= '</div>'.DOKU_LF; 168d74aace9Schris } 1690cecf9d5Sandi } 170a2d649c4Sandi $this->doc .= '</div>'.DOKU_LF; 1710cecf9d5Sandi } 172c5a8fd96SAndreas Gohr 173b8595a66SAndreas Gohr // Prepare the TOC 174851f2e89SAnika Henke global $conf; 17564159a61SAndreas Gohr if( 17664159a61SAndreas Gohr $this->info['toc'] && 17764159a61SAndreas Gohr is_array($this->toc) && 17864159a61SAndreas Gohr $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] 17964159a61SAndreas Gohr ) { 180b8595a66SAndreas Gohr global $TOC; 181b8595a66SAndreas Gohr $TOC = $this->toc; 1820cecf9d5Sandi } 1833e55d035SAndreas Gohr 1843e55d035SAndreas Gohr // make sure there are no empty paragraphs 18527918226Schris $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 186e41c4da9SAndreas Gohr } 1870cecf9d5Sandi 1883dd5c225SAndreas Gohr /** 1893dd5c225SAndreas Gohr * Add an item to the TOC 1903dd5c225SAndreas Gohr * 1913dd5c225SAndreas Gohr * @param string $id the hash link 1923dd5c225SAndreas Gohr * @param string $text the text to display 1933dd5c225SAndreas Gohr * @param int $level the nesting level 1943dd5c225SAndreas Gohr */ 195de369923SAndreas Gohr public function toc_additem($id, $text, $level) { 196af587fa8Sandi global $conf; 197af587fa8Sandi 198c5a8fd96SAndreas Gohr //handle TOC 199c5a8fd96SAndreas Gohr if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 2007d91652aSAndreas Gohr $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 201c5a8fd96SAndreas Gohr } 202e7856beaSchris } 203e7856beaSchris 2043dd5c225SAndreas Gohr /** 2053dd5c225SAndreas Gohr * Render a heading 2063dd5c225SAndreas Gohr * 2073dd5c225SAndreas Gohr * @param string $text the text to display 2083dd5c225SAndreas Gohr * @param int $level header level 2093dd5c225SAndreas Gohr * @param int $pos byte position in the original source 210e3c00e6eSIain Hallam * @param bool $returnonly whether to return html or write to doc attribute 211e3c00e6eSIain Hallam * @return void|string writes to doc attribute or returns html depends on $returnonly 2123dd5c225SAndreas Gohr */ 213e3c00e6eSIain Hallam public function header($text, $level, $pos, $returnonly = false) { 21490df9a4dSAdrian Lang global $conf; 21590df9a4dSAdrian Lang 216f515db7fSAndreas Gohr if(blank($text)) return; //skip empty headlines 217e7856beaSchris 218e7856beaSchris $hid = $this->_headerToLink($text, true); 219e7856beaSchris 220e7856beaSchris //only add items within configured levels 221e7856beaSchris $this->toc_additem($hid, $text, $level); 222c5a8fd96SAndreas Gohr 22391459163SAnika Henke // adjust $node to reflect hierarchy of levels 22491459163SAnika Henke $this->node[$level - 1]++; 22591459163SAnika Henke if($level < $this->lastlevel) { 22691459163SAnika Henke for($i = 0; $i < $this->lastlevel - $level; $i++) { 22791459163SAnika Henke $this->node[$this->lastlevel - $i - 1] = 0; 22891459163SAnika Henke } 22991459163SAnika Henke } 23091459163SAnika Henke $this->lastlevel = $level; 23191459163SAnika Henke 23290df9a4dSAdrian Lang if($level <= $conf['maxseclevel'] && 23390df9a4dSAdrian Lang count($this->sectionedits) > 0 && 234ec57f119SLarsDW223 $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' 2353dd5c225SAndreas Gohr ) { 2366c1f778cSAdrian Lang $this->finishSectionEdit($pos - 1); 23790df9a4dSAdrian Lang } 23890df9a4dSAdrian Lang 239e3c00e6eSIain Hallam // build the header 240e3c00e6eSIain Hallam $header = DOKU_LF.'<h'.$level; 24190df9a4dSAdrian Lang if($level <= $conf['maxseclevel']) { 242ec57f119SLarsDW223 $data = array(); 243ec57f119SLarsDW223 $data['target'] = 'section'; 244ec57f119SLarsDW223 $data['name'] = $text; 245ec57f119SLarsDW223 $data['hid'] = $hid; 246ec57f119SLarsDW223 $data['codeblockOffset'] = $this->_codeblock; 247e3c00e6eSIain Hallam $header .= ' class="'.$this->startSectionEdit($pos, $data).'"'; 24890df9a4dSAdrian Lang } 249e3c00e6eSIain Hallam $header .= ' id="'.$hid.'">'; 250e3c00e6eSIain Hallam $header .= $this->_xmlEntities($text); 251e3c00e6eSIain Hallam $header .= "</h$level>".DOKU_LF; 252e3c00e6eSIain Hallam 253e3c00e6eSIain Hallam if ($returnonly) { 254e3c00e6eSIain Hallam return $header; 255e3c00e6eSIain Hallam } else { 256e3c00e6eSIain Hallam $this->doc .= $header; 257e3c00e6eSIain Hallam } 2580cecf9d5Sandi } 2590cecf9d5Sandi 2603dd5c225SAndreas Gohr /** 2613dd5c225SAndreas Gohr * Open a new section 2623dd5c225SAndreas Gohr * 2633dd5c225SAndreas Gohr * @param int $level section level (as determined by the previous header) 2643dd5c225SAndreas Gohr */ 265de369923SAndreas Gohr public function section_open($level) { 2669864e7b1SAdrian Lang $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; 2670cecf9d5Sandi } 2680cecf9d5Sandi 2693dd5c225SAndreas Gohr /** 2703dd5c225SAndreas Gohr * Close the current section 2713dd5c225SAndreas Gohr */ 272de369923SAndreas Gohr public function section_close() { 273a2d649c4Sandi $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 2740cecf9d5Sandi } 2750cecf9d5Sandi 2763dd5c225SAndreas Gohr /** 2773dd5c225SAndreas Gohr * Render plain text data 2783dd5c225SAndreas Gohr * 2793dd5c225SAndreas Gohr * @param $text 2803dd5c225SAndreas Gohr */ 281de369923SAndreas Gohr public function cdata($text) { 282a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 2830cecf9d5Sandi } 2840cecf9d5Sandi 2853dd5c225SAndreas Gohr /** 2863dd5c225SAndreas Gohr * Open a paragraph 2873dd5c225SAndreas Gohr */ 288de369923SAndreas Gohr public function p_open() { 28959869a4bSAnika Henke $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 2900cecf9d5Sandi } 2910cecf9d5Sandi 2923dd5c225SAndreas Gohr /** 2933dd5c225SAndreas Gohr * Close a paragraph 2943dd5c225SAndreas Gohr */ 295de369923SAndreas Gohr public function p_close() { 29659869a4bSAnika Henke $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 2970cecf9d5Sandi } 2980cecf9d5Sandi 2993dd5c225SAndreas Gohr /** 3003dd5c225SAndreas Gohr * Create a line break 3013dd5c225SAndreas Gohr */ 302de369923SAndreas Gohr public function linebreak() { 303a2d649c4Sandi $this->doc .= '<br/>'.DOKU_LF; 3040cecf9d5Sandi } 3050cecf9d5Sandi 3063dd5c225SAndreas Gohr /** 3073dd5c225SAndreas Gohr * Create a horizontal line 3083dd5c225SAndreas Gohr */ 309de369923SAndreas Gohr public function hr() { 3104beabca9SAnika Henke $this->doc .= '<hr />'.DOKU_LF; 3110cecf9d5Sandi } 3120cecf9d5Sandi 3133dd5c225SAndreas Gohr /** 3143dd5c225SAndreas Gohr * Start strong (bold) formatting 3153dd5c225SAndreas Gohr */ 316de369923SAndreas Gohr public function strong_open() { 317a2d649c4Sandi $this->doc .= '<strong>'; 3180cecf9d5Sandi } 3190cecf9d5Sandi 3203dd5c225SAndreas Gohr /** 3213dd5c225SAndreas Gohr * Stop strong (bold) formatting 3223dd5c225SAndreas Gohr */ 323de369923SAndreas Gohr public function strong_close() { 324a2d649c4Sandi $this->doc .= '</strong>'; 3250cecf9d5Sandi } 3260cecf9d5Sandi 3273dd5c225SAndreas Gohr /** 3283dd5c225SAndreas Gohr * Start emphasis (italics) formatting 3293dd5c225SAndreas Gohr */ 330de369923SAndreas Gohr public function emphasis_open() { 331a2d649c4Sandi $this->doc .= '<em>'; 3320cecf9d5Sandi } 3330cecf9d5Sandi 3343dd5c225SAndreas Gohr /** 3353dd5c225SAndreas Gohr * Stop emphasis (italics) formatting 3363dd5c225SAndreas Gohr */ 337de369923SAndreas Gohr public function emphasis_close() { 338a2d649c4Sandi $this->doc .= '</em>'; 3390cecf9d5Sandi } 3400cecf9d5Sandi 3413dd5c225SAndreas Gohr /** 3423dd5c225SAndreas Gohr * Start underline formatting 3433dd5c225SAndreas Gohr */ 344de369923SAndreas Gohr public function underline_open() { 34502e51121SAnika Henke $this->doc .= '<em class="u">'; 3460cecf9d5Sandi } 3470cecf9d5Sandi 3483dd5c225SAndreas Gohr /** 3493dd5c225SAndreas Gohr * Stop underline formatting 3503dd5c225SAndreas Gohr */ 351de369923SAndreas Gohr public function underline_close() { 35202e51121SAnika Henke $this->doc .= '</em>'; 3530cecf9d5Sandi } 3540cecf9d5Sandi 3553dd5c225SAndreas Gohr /** 3563dd5c225SAndreas Gohr * Start monospace formatting 3573dd5c225SAndreas Gohr */ 358de369923SAndreas Gohr public function monospace_open() { 359a2d649c4Sandi $this->doc .= '<code>'; 3600cecf9d5Sandi } 3610cecf9d5Sandi 3623dd5c225SAndreas Gohr /** 3633dd5c225SAndreas Gohr * Stop monospace formatting 3643dd5c225SAndreas Gohr */ 365de369923SAndreas Gohr public function monospace_close() { 366a2d649c4Sandi $this->doc .= '</code>'; 3670cecf9d5Sandi } 3680cecf9d5Sandi 3693dd5c225SAndreas Gohr /** 3703dd5c225SAndreas Gohr * Start a subscript 3713dd5c225SAndreas Gohr */ 372de369923SAndreas Gohr public function subscript_open() { 373a2d649c4Sandi $this->doc .= '<sub>'; 3740cecf9d5Sandi } 3750cecf9d5Sandi 3763dd5c225SAndreas Gohr /** 3773dd5c225SAndreas Gohr * Stop a subscript 3783dd5c225SAndreas Gohr */ 379de369923SAndreas Gohr public function subscript_close() { 380a2d649c4Sandi $this->doc .= '</sub>'; 3810cecf9d5Sandi } 3820cecf9d5Sandi 3833dd5c225SAndreas Gohr /** 3843dd5c225SAndreas Gohr * Start a superscript 3853dd5c225SAndreas Gohr */ 386de369923SAndreas Gohr public function superscript_open() { 387a2d649c4Sandi $this->doc .= '<sup>'; 3880cecf9d5Sandi } 3890cecf9d5Sandi 3903dd5c225SAndreas Gohr /** 3913dd5c225SAndreas Gohr * Stop a superscript 3923dd5c225SAndreas Gohr */ 393de369923SAndreas Gohr public function superscript_close() { 394a2d649c4Sandi $this->doc .= '</sup>'; 3950cecf9d5Sandi } 3960cecf9d5Sandi 3973dd5c225SAndreas Gohr /** 3983dd5c225SAndreas Gohr * Start deleted (strike-through) formatting 3993dd5c225SAndreas Gohr */ 400de369923SAndreas Gohr public function deleted_open() { 401a2d649c4Sandi $this->doc .= '<del>'; 4020cecf9d5Sandi } 4030cecf9d5Sandi 4043dd5c225SAndreas Gohr /** 4053dd5c225SAndreas Gohr * Stop deleted (strike-through) formatting 4063dd5c225SAndreas Gohr */ 407de369923SAndreas Gohr public function deleted_close() { 408a2d649c4Sandi $this->doc .= '</del>'; 4090cecf9d5Sandi } 4100cecf9d5Sandi 4113fd0b676Sandi /** 4123fd0b676Sandi * Callback for footnote start syntax 4133fd0b676Sandi * 4143fd0b676Sandi * All following content will go to the footnote instead of 415d74aace9Schris * the document. To achieve this the previous rendered content 4163fd0b676Sandi * is moved to $store and $doc is cleared 4173fd0b676Sandi * 4183fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 4193fd0b676Sandi */ 420de369923SAndreas Gohr public function footnote_open() { 4217764a90aSandi 4227764a90aSandi // move current content to store and record footnote 4237764a90aSandi $this->store = $this->doc; 4247764a90aSandi $this->doc = ''; 4250cecf9d5Sandi } 4260cecf9d5Sandi 4273fd0b676Sandi /** 4283fd0b676Sandi * Callback for footnote end syntax 4293fd0b676Sandi * 4303fd0b676Sandi * All rendered content is moved to the $footnotes array and the old 4313fd0b676Sandi * content is restored from $store again 4323fd0b676Sandi * 4333fd0b676Sandi * @author Andreas Gohr 4343fd0b676Sandi */ 435de369923SAndreas Gohr public function footnote_close() { 43616ec3e37SAndreas Gohr /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 43716ec3e37SAndreas Gohr static $fnid = 0; 43816ec3e37SAndreas Gohr // assign new footnote id (we start at 1) 43916ec3e37SAndreas Gohr $fnid++; 4407764a90aSandi 441d74aace9Schris // recover footnote into the stack and restore old content 442d74aace9Schris $footnote = $this->doc; 4437764a90aSandi $this->doc = $this->store; 4447764a90aSandi $this->store = ''; 445d74aace9Schris 446d74aace9Schris // check to see if this footnote has been seen before 447d74aace9Schris $i = array_search($footnote, $this->footnotes); 448d74aace9Schris 449d74aace9Schris if($i === false) { 450d74aace9Schris // its a new footnote, add it to the $footnotes array 45116ec3e37SAndreas Gohr $this->footnotes[$fnid] = $footnote; 452d74aace9Schris } else { 45316ec3e37SAndreas Gohr // seen this one before, save a placeholder 45416ec3e37SAndreas Gohr $this->footnotes[$fnid] = "@@FNT".($i); 455d74aace9Schris } 456d74aace9Schris 4576b379cbfSAndreas Gohr // output the footnote reference and link 45816ec3e37SAndreas Gohr $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>'; 4590cecf9d5Sandi } 4600cecf9d5Sandi 4613dd5c225SAndreas Gohr /** 4623dd5c225SAndreas Gohr * Open an unordered list 4630c4c0281SGerrit Uitslag * 4647d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4653dd5c225SAndreas Gohr */ 466de369923SAndreas Gohr public function listu_open($classes = null) { 4670c4c0281SGerrit Uitslag $class = ''; 4680c4c0281SGerrit Uitslag if($classes !== null) { 4692e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4700c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4710c4c0281SGerrit Uitslag } 4720c4c0281SGerrit Uitslag $this->doc .= "<ul$class>".DOKU_LF; 4730cecf9d5Sandi } 4740cecf9d5Sandi 4753dd5c225SAndreas Gohr /** 4763dd5c225SAndreas Gohr * Close an unordered list 4773dd5c225SAndreas Gohr */ 478de369923SAndreas Gohr public function listu_close() { 479a2d649c4Sandi $this->doc .= '</ul>'.DOKU_LF; 4800cecf9d5Sandi } 4810cecf9d5Sandi 4823dd5c225SAndreas Gohr /** 4833dd5c225SAndreas Gohr * Open an ordered list 4840c4c0281SGerrit Uitslag * 4857d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4863dd5c225SAndreas Gohr */ 487de369923SAndreas Gohr public function listo_open($classes = null) { 4880c4c0281SGerrit Uitslag $class = ''; 4890c4c0281SGerrit Uitslag if($classes !== null) { 4902e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4910c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4920c4c0281SGerrit Uitslag } 4930c4c0281SGerrit Uitslag $this->doc .= "<ol$class>".DOKU_LF; 4940cecf9d5Sandi } 4950cecf9d5Sandi 4963dd5c225SAndreas Gohr /** 4973dd5c225SAndreas Gohr * Close an ordered list 4983dd5c225SAndreas Gohr */ 499de369923SAndreas Gohr public function listo_close() { 500a2d649c4Sandi $this->doc .= '</ol>'.DOKU_LF; 5010cecf9d5Sandi } 5020cecf9d5Sandi 5033dd5c225SAndreas Gohr /** 5043dd5c225SAndreas Gohr * Open a list item 5053dd5c225SAndreas Gohr * 5063dd5c225SAndreas Gohr * @param int $level the nesting level 507e3a24861SChristopher Smith * @param bool $node true when a node; false when a leaf 5083dd5c225SAndreas Gohr */ 509de369923SAndreas Gohr public function listitem_open($level, $node=false) { 510e3a24861SChristopher Smith $branching = $node ? ' node' : ''; 511e3a24861SChristopher Smith $this->doc .= '<li class="level'.$level.$branching.'">'; 5120cecf9d5Sandi } 5130cecf9d5Sandi 5143dd5c225SAndreas Gohr /** 5153dd5c225SAndreas Gohr * Close a list item 5163dd5c225SAndreas Gohr */ 517de369923SAndreas Gohr public function listitem_close() { 518a2d649c4Sandi $this->doc .= '</li>'.DOKU_LF; 5190cecf9d5Sandi } 5200cecf9d5Sandi 5213dd5c225SAndreas Gohr /** 5223dd5c225SAndreas Gohr * Start the content of a list item 5233dd5c225SAndreas Gohr */ 524de369923SAndreas Gohr public function listcontent_open() { 52590db23d7Schris $this->doc .= '<div class="li">'; 5260cecf9d5Sandi } 5270cecf9d5Sandi 5283dd5c225SAndreas Gohr /** 5293dd5c225SAndreas Gohr * Stop the content of a list item 5303dd5c225SAndreas Gohr */ 531de369923SAndreas Gohr public function listcontent_close() { 53259869a4bSAnika Henke $this->doc .= '</div>'.DOKU_LF; 5330cecf9d5Sandi } 5340cecf9d5Sandi 5353dd5c225SAndreas Gohr /** 5363dd5c225SAndreas Gohr * Output unformatted $text 5373dd5c225SAndreas Gohr * 5383dd5c225SAndreas Gohr * Defaults to $this->cdata() 5393dd5c225SAndreas Gohr * 5403dd5c225SAndreas Gohr * @param string $text 5413dd5c225SAndreas Gohr */ 542de369923SAndreas Gohr public function unformatted($text) { 543a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 5440cecf9d5Sandi } 5450cecf9d5Sandi 5460cecf9d5Sandi /** 5473fd0b676Sandi * Execute PHP code if allowed 5483fd0b676Sandi * 549d9764001SMichael Hamann * @param string $text PHP code that is either executed or printed 5505d568b99SChris Smith * @param string $wrapper html element to wrap result if $conf['phpok'] is okff 5515d568b99SChris Smith * 5523fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 5530cecf9d5Sandi */ 554de369923SAndreas Gohr public function php($text, $wrapper = 'code') { 55535a56260SChris Smith global $conf; 55635a56260SChris Smith 557d86d5af0SChris Smith if($conf['phpok']) { 558bad0b545Sandi ob_start(); 5594de671bcSandi eval($text); 5603fd0b676Sandi $this->doc .= ob_get_contents(); 561bad0b545Sandi ob_end_clean(); 562d86d5af0SChris Smith } else { 5635d568b99SChris Smith $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 564d86d5af0SChris Smith } 5650cecf9d5Sandi } 5660cecf9d5Sandi 5673dd5c225SAndreas Gohr /** 5683dd5c225SAndreas Gohr * Output block level PHP code 5693dd5c225SAndreas Gohr * 5703dd5c225SAndreas Gohr * If $conf['phpok'] is true this should evaluate the given code and append the result 5713dd5c225SAndreas Gohr * to $doc 5723dd5c225SAndreas Gohr * 5733dd5c225SAndreas Gohr * @param string $text The PHP code 5743dd5c225SAndreas Gohr */ 575de369923SAndreas Gohr public function phpblock($text) { 5765d568b99SChris Smith $this->php($text, 'pre'); 57707f89c3cSAnika Henke } 57807f89c3cSAnika Henke 5790cecf9d5Sandi /** 5803fd0b676Sandi * Insert HTML if allowed 5813fd0b676Sandi * 582d9764001SMichael Hamann * @param string $text html text 5835d568b99SChris Smith * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff 5845d568b99SChris Smith * 5853fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 5860cecf9d5Sandi */ 587de369923SAndreas Gohr public function html($text, $wrapper = 'code') { 58835a56260SChris Smith global $conf; 58935a56260SChris Smith 590d86d5af0SChris Smith if($conf['htmlok']) { 591a2d649c4Sandi $this->doc .= $text; 592d86d5af0SChris Smith } else { 5935d568b99SChris Smith $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 594d86d5af0SChris Smith } 5954de671bcSandi } 5960cecf9d5Sandi 5973dd5c225SAndreas Gohr /** 5983dd5c225SAndreas Gohr * Output raw block-level HTML 5993dd5c225SAndreas Gohr * 6003dd5c225SAndreas Gohr * If $conf['htmlok'] is true this should add the code as is to $doc 6013dd5c225SAndreas Gohr * 6023dd5c225SAndreas Gohr * @param string $text The HTML 6033dd5c225SAndreas Gohr */ 604de369923SAndreas Gohr public function htmlblock($text) { 6055d568b99SChris Smith $this->html($text, 'pre'); 60607f89c3cSAnika Henke } 60707f89c3cSAnika Henke 6083dd5c225SAndreas Gohr /** 6093dd5c225SAndreas Gohr * Start a block quote 6103dd5c225SAndreas Gohr */ 611de369923SAndreas Gohr public function quote_open() { 61296331712SAnika Henke $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 6130cecf9d5Sandi } 6140cecf9d5Sandi 6153dd5c225SAndreas Gohr /** 6163dd5c225SAndreas Gohr * Stop a block quote 6173dd5c225SAndreas Gohr */ 618de369923SAndreas Gohr public function quote_close() { 61996331712SAnika Henke $this->doc .= '</div></blockquote>'.DOKU_LF; 6200cecf9d5Sandi } 6210cecf9d5Sandi 6223dd5c225SAndreas Gohr /** 6233dd5c225SAndreas Gohr * Output preformatted text 6243dd5c225SAndreas Gohr * 6253dd5c225SAndreas Gohr * @param string $text 6263dd5c225SAndreas Gohr */ 627de369923SAndreas Gohr public function preformatted($text) { 628c9250713SAnika Henke $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; 6293d491f75SAndreas Gohr } 6303d491f75SAndreas Gohr 6313dd5c225SAndreas Gohr /** 6323dd5c225SAndreas Gohr * Display text as file content, optionally syntax highlighted 6333dd5c225SAndreas Gohr * 6343dd5c225SAndreas Gohr * @param string $text text to show 6353dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6363dd5c225SAndreas Gohr * @param string $filename file path label 637e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6383dd5c225SAndreas Gohr */ 639de369923SAndreas Gohr public function file($text, $language = null, $filename = null, $options=null) { 640e2d88156SLarsDW223 $this->_highlight('file', $text, $language, $filename, $options); 6413d491f75SAndreas Gohr } 6423d491f75SAndreas Gohr 6433dd5c225SAndreas Gohr /** 6443dd5c225SAndreas Gohr * Display text as code content, optionally syntax highlighted 6453dd5c225SAndreas Gohr * 6463dd5c225SAndreas Gohr * @param string $text text to show 6473dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6483dd5c225SAndreas Gohr * @param string $filename file path label 649e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6503dd5c225SAndreas Gohr */ 651de369923SAndreas Gohr public function code($text, $language = null, $filename = null, $options=null) { 652e2d88156SLarsDW223 $this->_highlight('code', $text, $language, $filename, $options); 6533d491f75SAndreas Gohr } 6543d491f75SAndreas Gohr 6550cecf9d5Sandi /** 6563d491f75SAndreas Gohr * Use GeSHi to highlight language syntax in code and file blocks 6573fd0b676Sandi * 6583fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 6593dd5c225SAndreas Gohr * @param string $type code|file 6603dd5c225SAndreas Gohr * @param string $text text to show 6613dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6623dd5c225SAndreas Gohr * @param string $filename file path label 663e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6640cecf9d5Sandi */ 665de369923SAndreas Gohr public function _highlight($type, $text, $language = null, $filename = null, $options = null) { 6663d491f75SAndreas Gohr global $ID; 6673d491f75SAndreas Gohr global $lang; 668ec57f119SLarsDW223 global $INPUT; 6693d491f75SAndreas Gohr 670bf8f8509SAndreas Gohr $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language ?? ''); 67156bd9509SPhy 6723d491f75SAndreas Gohr if($filename) { 673190c56e8SAndreas Gohr // add icon 67427bf7924STom N Harris list($ext) = mimetype($filename, false); 675190c56e8SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 676190c56e8SAndreas Gohr $class = 'mediafile mf_'.$class; 677190c56e8SAndreas Gohr 678ec57f119SLarsDW223 $offset = 0; 679ec57f119SLarsDW223 if ($INPUT->has('codeblockOffset')) { 680ec57f119SLarsDW223 $offset = $INPUT->str('codeblockOffset'); 681ec57f119SLarsDW223 } 6823d491f75SAndreas Gohr $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 68364159a61SAndreas Gohr $this->doc .= '<dt><a href="' . 68464159a61SAndreas Gohr exportlink( 68564159a61SAndreas Gohr $ID, 68664159a61SAndreas Gohr 'code', 68764159a61SAndreas Gohr array('codeblock' => $offset + $this->_codeblock) 68864159a61SAndreas Gohr ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; 6893d491f75SAndreas Gohr $this->doc .= hsc($filename); 6903d491f75SAndreas Gohr $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 6913d491f75SAndreas Gohr } 6920cecf9d5Sandi 6932401f18dSSyntaxseed if($text[0] == "\n") { 694d43aac1cSGina Haeussge $text = substr($text, 1); 695d43aac1cSGina Haeussge } 696d43aac1cSGina Haeussge if(substr($text, -1) == "\n") { 697d43aac1cSGina Haeussge $text = substr($text, 0, -1); 698d43aac1cSGina Haeussge } 699d43aac1cSGina Haeussge 700a056e285SPhy if(empty($language)) { // empty is faster than is_null and can prevent '' string 7013d491f75SAndreas Gohr $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF; 7020cecf9d5Sandi } else { 7033d491f75SAndreas Gohr $class = 'code'; //we always need the code class to make the syntax highlighting apply 7043d491f75SAndreas Gohr if($type != 'code') $class .= ' '.$type; 7053d491f75SAndreas Gohr 70664159a61SAndreas Gohr $this->doc .= "<pre class=\"$class $language\">" . 70764159a61SAndreas Gohr p_xhtml_cached_geshi($text, $language, '', $options) . 70864159a61SAndreas Gohr '</pre>' . DOKU_LF; 7090cecf9d5Sandi } 7103d491f75SAndreas Gohr 7113d491f75SAndreas Gohr if($filename) { 7123d491f75SAndreas Gohr $this->doc .= '</dd></dl>'.DOKU_LF; 7133d491f75SAndreas Gohr } 7143d491f75SAndreas Gohr 7153d491f75SAndreas Gohr $this->_codeblock++; 7160cecf9d5Sandi } 7170cecf9d5Sandi 7183dd5c225SAndreas Gohr /** 7193dd5c225SAndreas Gohr * Format an acronym 7203dd5c225SAndreas Gohr * 7213dd5c225SAndreas Gohr * Uses $this->acronyms 7223dd5c225SAndreas Gohr * 7233dd5c225SAndreas Gohr * @param string $acronym 7243dd5c225SAndreas Gohr */ 725de369923SAndreas Gohr public function acronym($acronym) { 7260cecf9d5Sandi 7270cecf9d5Sandi if(array_key_exists($acronym, $this->acronyms)) { 7280cecf9d5Sandi 729433bef32Sandi $title = $this->_xmlEntities($this->acronyms[$acronym]); 7300cecf9d5Sandi 731940db3a3SAnika Henke $this->doc .= '<abbr title="'.$title 732940db3a3SAnika Henke .'">'.$this->_xmlEntities($acronym).'</abbr>'; 7330cecf9d5Sandi 7340cecf9d5Sandi } else { 735a2d649c4Sandi $this->doc .= $this->_xmlEntities($acronym); 7360cecf9d5Sandi } 7370cecf9d5Sandi } 7380cecf9d5Sandi 7393dd5c225SAndreas Gohr /** 7403dd5c225SAndreas Gohr * Format a smiley 7413dd5c225SAndreas Gohr * 7423dd5c225SAndreas Gohr * Uses $this->smiley 7433dd5c225SAndreas Gohr * 7443dd5c225SAndreas Gohr * @param string $smiley 7453dd5c225SAndreas Gohr */ 746de369923SAndreas Gohr public function smiley($smiley) { 747b09504a9SAndreas Gohr if (isset($this->smileys[$smiley])) { 748f62ea8a1Sandi $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] . 749b09504a9SAndreas Gohr '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />'; 7500cecf9d5Sandi } else { 751a2d649c4Sandi $this->doc .= $this->_xmlEntities($smiley); 7520cecf9d5Sandi } 7530cecf9d5Sandi } 7540cecf9d5Sandi 7553dd5c225SAndreas Gohr /** 7563dd5c225SAndreas Gohr * Format an entity 7573dd5c225SAndreas Gohr * 7583dd5c225SAndreas Gohr * Entities are basically small text replacements 7593dd5c225SAndreas Gohr * 7603dd5c225SAndreas Gohr * Uses $this->entities 7613dd5c225SAndreas Gohr * 7623dd5c225SAndreas Gohr * @param string $entity 7634de671bcSandi */ 764de369923SAndreas Gohr public function entity($entity) { 7650cecf9d5Sandi if(array_key_exists($entity, $this->entities)) { 766a2d649c4Sandi $this->doc .= $this->entities[$entity]; 7670cecf9d5Sandi } else { 768a2d649c4Sandi $this->doc .= $this->_xmlEntities($entity); 7690cecf9d5Sandi } 7700cecf9d5Sandi } 7710cecf9d5Sandi 7723dd5c225SAndreas Gohr /** 7733dd5c225SAndreas Gohr * Typographically format a multiply sign 7743dd5c225SAndreas Gohr * 7753dd5c225SAndreas Gohr * Example: ($x=640, $y=480) should result in "640×480" 7763dd5c225SAndreas Gohr * 7773dd5c225SAndreas Gohr * @param string|int $x first value 7783dd5c225SAndreas Gohr * @param string|int $y second value 7793dd5c225SAndreas Gohr */ 780de369923SAndreas Gohr public function multiplyentity($x, $y) { 781a2d649c4Sandi $this->doc .= "$x×$y"; 7820cecf9d5Sandi } 7830cecf9d5Sandi 7843dd5c225SAndreas Gohr /** 7853dd5c225SAndreas Gohr * Render an opening single quote char (language specific) 7863dd5c225SAndreas Gohr */ 787de369923SAndreas Gohr public function singlequoteopening() { 78871b40da2SAnika Henke global $lang; 78971b40da2SAnika Henke $this->doc .= $lang['singlequoteopening']; 7900cecf9d5Sandi } 7910cecf9d5Sandi 7923dd5c225SAndreas Gohr /** 7933dd5c225SAndreas Gohr * Render a closing single quote char (language specific) 7943dd5c225SAndreas Gohr */ 795de369923SAndreas Gohr public function singlequoteclosing() { 79671b40da2SAnika Henke global $lang; 79771b40da2SAnika Henke $this->doc .= $lang['singlequoteclosing']; 7980cecf9d5Sandi } 7990cecf9d5Sandi 8003dd5c225SAndreas Gohr /** 8013dd5c225SAndreas Gohr * Render an apostrophe char (language specific) 8023dd5c225SAndreas Gohr */ 803de369923SAndreas Gohr public function apostrophe() { 80457d757d1SAndreas Gohr global $lang; 805a8bd192aSAndreas Gohr $this->doc .= $lang['apostrophe']; 80657d757d1SAndreas Gohr } 80757d757d1SAndreas Gohr 8083dd5c225SAndreas Gohr /** 8093dd5c225SAndreas Gohr * Render an opening double quote char (language specific) 8103dd5c225SAndreas Gohr */ 811de369923SAndreas Gohr public function doublequoteopening() { 81271b40da2SAnika Henke global $lang; 81371b40da2SAnika Henke $this->doc .= $lang['doublequoteopening']; 8140cecf9d5Sandi } 8150cecf9d5Sandi 8163dd5c225SAndreas Gohr /** 8173dd5c225SAndreas Gohr * Render an closinging double quote char (language specific) 8183dd5c225SAndreas Gohr */ 819de369923SAndreas Gohr public function doublequoteclosing() { 82071b40da2SAnika Henke global $lang; 82171b40da2SAnika Henke $this->doc .= $lang['doublequoteclosing']; 8220cecf9d5Sandi } 8230cecf9d5Sandi 8240cecf9d5Sandi /** 8253dd5c225SAndreas Gohr * Render a CamelCase link 8263dd5c225SAndreas Gohr * 8273dd5c225SAndreas Gohr * @param string $link The link name 828122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8290c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8300c4c0281SGerrit Uitslag * 8313dd5c225SAndreas Gohr * @see http://en.wikipedia.org/wiki/CamelCase 8320cecf9d5Sandi */ 833de369923SAndreas Gohr public function camelcaselink($link, $returnonly = false) { 834122f2d46SAndreas Böhler if($returnonly) { 835122f2d46SAndreas Böhler return $this->internallink($link, $link, null, true); 836122f2d46SAndreas Böhler } else { 83711d0aa47Sandi $this->internallink($link, $link); 8380cecf9d5Sandi } 839122f2d46SAndreas Böhler } 8400cecf9d5Sandi 8413dd5c225SAndreas Gohr /** 8423dd5c225SAndreas Gohr * Render a page local link 8433dd5c225SAndreas Gohr * 8443dd5c225SAndreas Gohr * @param string $hash hash link identifier 8453dd5c225SAndreas Gohr * @param string $name name for the link 846122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8470c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8483dd5c225SAndreas Gohr */ 849de369923SAndreas Gohr public function locallink($hash, $name = null, $returnonly = false) { 8500b7c14c2Sandi global $ID; 8510b7c14c2Sandi $name = $this->_getLinkTitle($name, $hash, $isImage); 8520b7c14c2Sandi $hash = $this->_headerToLink($hash); 853e260f93bSAnika Henke $title = $ID.' ↵'; 854122f2d46SAndreas Böhler 855122f2d46SAndreas Böhler $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 856122f2d46SAndreas Böhler $doc .= $name; 857122f2d46SAndreas Böhler $doc .= '</a>'; 858122f2d46SAndreas Böhler 859122f2d46SAndreas Böhler if($returnonly) { 860122f2d46SAndreas Böhler return $doc; 861122f2d46SAndreas Böhler } else { 862122f2d46SAndreas Böhler $this->doc .= $doc; 863122f2d46SAndreas Böhler } 8640b7c14c2Sandi } 8650b7c14c2Sandi 866cffcc403Sandi /** 8673fd0b676Sandi * Render an internal Wiki Link 8683fd0b676Sandi * 869fe9ec250SChris Smith * $search,$returnonly & $linktype are not for the renderer but are used 870cffcc403Sandi * elsewhere - no need to implement them in other renderers 8713fd0b676Sandi * 8723dd5c225SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 873f23eef27SGerrit Uitslag * @param string $id pageid 874f23eef27SGerrit Uitslag * @param string|null $name link name 875f23eef27SGerrit Uitslag * @param string|null $search adds search url param 876f23eef27SGerrit Uitslag * @param bool $returnonly whether to return html or write to doc attribute 877f23eef27SGerrit Uitslag * @param string $linktype type to set use of headings 878f23eef27SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 879cffcc403Sandi */ 880de369923SAndreas Gohr public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { 881ba11bd29Sandi global $conf; 88237e34a5eSandi global $ID; 883c4dda6afSAnika Henke global $INFO; 88444653a53SAdrian Lang 8853d5e07d9SAdrian Lang $params = ''; 8863d5e07d9SAdrian Lang $parts = explode('?', $id, 2); 8873d5e07d9SAdrian Lang if(count($parts) === 2) { 8883d5e07d9SAdrian Lang $id = $parts[0]; 8893d5e07d9SAdrian Lang $params = $parts[1]; 89044653a53SAdrian Lang } 89144653a53SAdrian Lang 892fda14ffcSIzidor Matušov // For empty $id we need to know the current $ID 893fda14ffcSIzidor Matušov // We need this check because _simpleTitle needs 894fda14ffcSIzidor Matušov // correct $id and resolve_pageid() use cleanID($id) 895fda14ffcSIzidor Matušov // (some things could be lost) 896fda14ffcSIzidor Matušov if($id === '') { 897fda14ffcSIzidor Matušov $id = $ID; 898fda14ffcSIzidor Matušov } 899fda14ffcSIzidor Matušov 9000339c872Sjan // default name is based on $id as given 9010339c872Sjan $default = $this->_simpleTitle($id); 902ad32e47eSAndreas Gohr 9030339c872Sjan // now first resolve and clean up the $id 9048c6be208SAndreas Gohr $id = (new PageResolver($ID))->resolveId($id, $this->date_at, true); 9058c6be208SAndreas Gohr $exists = page_exists($id, $this->date_at, false, true); 906fda14ffcSIzidor Matušov 90759bc3b48SGerrit Uitslag $link = array(); 908fe9ec250SChris Smith $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 9090e1c636eSandi if(!$isImage) { 9100e1c636eSandi if($exists) { 911ba11bd29Sandi $class = 'wikilink1'; 9120cecf9d5Sandi } else { 913ba11bd29Sandi $class = 'wikilink2'; 91444a6b4c7SAndreas Gohr $link['rel'] = 'nofollow'; 9150cecf9d5Sandi } 9160cecf9d5Sandi } else { 917ba11bd29Sandi $class = 'media'; 9180cecf9d5Sandi } 9190cecf9d5Sandi 920a1685bedSandi //keep hash anchor 921*ec34bb30SAndreas Gohr list($id, $hash) = sexplode('#', $id, 2); 922943dedc6SAndreas Gohr if(!empty($hash)) $hash = $this->_headerToLink($hash); 923a1685bedSandi 924ba11bd29Sandi //prepare for formating 925ba11bd29Sandi $link['target'] = $conf['target']['wiki']; 926ba11bd29Sandi $link['style'] = ''; 927ba11bd29Sandi $link['pre'] = ''; 928ba11bd29Sandi $link['suf'] = ''; 929bbac1489SPhy $link['more'] = 'data-wiki-id="'.$id.'"'; // id is already cleaned 930ba11bd29Sandi $link['class'] = $class; 9315c2eed9aSlisps if($this->date_at) { 932912a6d48SPhy $params = $params.'&at='.rawurlencode($this->date_at); 9335c2eed9aSlisps } 93444653a53SAdrian Lang $link['url'] = wl($id, $params); 935ba11bd29Sandi $link['name'] = $name; 936ba11bd29Sandi $link['title'] = $id; 937723d78dbSandi //add search string 938723d78dbSandi if($search) { 939546d3a99SAndreas Gohr ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 940546d3a99SAndreas Gohr if(is_array($search)) { 941546d3a99SAndreas Gohr $search = array_map('rawurlencode', $search); 942546d3a99SAndreas Gohr $link['url'] .= 's[]='.join('&s[]=', $search); 943546d3a99SAndreas Gohr } else { 944546d3a99SAndreas Gohr $link['url'] .= 's='.rawurlencode($search); 945546d3a99SAndreas Gohr } 946723d78dbSandi } 947723d78dbSandi 948a1685bedSandi //keep hash 949a1685bedSandi if($hash) $link['url'] .= '#'.$hash; 950a1685bedSandi 951ba11bd29Sandi //output formatted 952cffcc403Sandi if($returnonly) { 953cffcc403Sandi return $this->_formatLink($link); 954cffcc403Sandi } else { 955a2d649c4Sandi $this->doc .= $this->_formatLink($link); 9560cecf9d5Sandi } 957cffcc403Sandi } 9580cecf9d5Sandi 9593dd5c225SAndreas Gohr /** 9603dd5c225SAndreas Gohr * Render an external link 9613dd5c225SAndreas Gohr * 9623dd5c225SAndreas Gohr * @param string $url full URL with scheme 9633dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 964122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9650c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9663dd5c225SAndreas Gohr */ 967de369923SAndreas Gohr public function externallink($url, $name = null, $returnonly = false) { 968b625487dSandi global $conf; 9690cecf9d5Sandi 970433bef32Sandi $name = $this->_getLinkTitle($name, $url, $isImage); 9716f0c5dbfSandi 972b52b1596SAndreas Gohr // url might be an attack vector, only allow registered protocols 973b52b1596SAndreas Gohr if(is_null($this->schemes)) $this->schemes = getSchemes(); 974b52b1596SAndreas Gohr list($scheme) = explode('://', $url); 975b52b1596SAndreas Gohr $scheme = strtolower($scheme); 976b52b1596SAndreas Gohr if(!in_array($scheme, $this->schemes)) $url = ''; 977b52b1596SAndreas Gohr 978b52b1596SAndreas Gohr // is there still an URL? 979b52b1596SAndreas Gohr if(!$url) { 98049cef4fdSAndreas Böhler if($returnonly) { 98149cef4fdSAndreas Böhler return $name; 98249cef4fdSAndreas Böhler } else { 983b52b1596SAndreas Gohr $this->doc .= $name; 98449cef4fdSAndreas Böhler } 985b52b1596SAndreas Gohr return; 986b52b1596SAndreas Gohr } 987b52b1596SAndreas Gohr 988b52b1596SAndreas Gohr // set class 9890cecf9d5Sandi if(!$isImage) { 990b625487dSandi $class = 'urlextern'; 9910cecf9d5Sandi } else { 992b625487dSandi $class = 'media'; 9930cecf9d5Sandi } 9940cecf9d5Sandi 995b625487dSandi //prepare for formating 99659bc3b48SGerrit Uitslag $link = array(); 997b625487dSandi $link['target'] = $conf['target']['extern']; 998b625487dSandi $link['style'] = ''; 999b625487dSandi $link['pre'] = ''; 1000b625487dSandi $link['suf'] = ''; 10015e163278SAndreas Gohr $link['more'] = ''; 1002b625487dSandi $link['class'] = $class; 1003b625487dSandi $link['url'] = $url; 1004914045f3SAndreas Gohr $link['rel'] = ''; 1005e1c10e4dSchris 1006b625487dSandi $link['name'] = $name; 1007433bef32Sandi $link['title'] = $this->_xmlEntities($url); 10085ddd0bbbSStarArmy if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; 1009914045f3SAndreas Gohr if($conf['target']['extern']) $link['rel'] .= ' noopener'; 10100cecf9d5Sandi 1011b625487dSandi //output formatted 1012122f2d46SAndreas Böhler if($returnonly) { 1013122f2d46SAndreas Böhler return $this->_formatLink($link); 1014122f2d46SAndreas Böhler } else { 1015a2d649c4Sandi $this->doc .= $this->_formatLink($link); 10160cecf9d5Sandi } 1017122f2d46SAndreas Böhler } 10180cecf9d5Sandi 10190cecf9d5Sandi /** 10203dd5c225SAndreas Gohr * Render an interwiki link 10213dd5c225SAndreas Gohr * 10223dd5c225SAndreas Gohr * You may want to use $this->_resolveInterWiki() here 10233dd5c225SAndreas Gohr * 10243dd5c225SAndreas Gohr * @param string $match original link - probably not much use 10253dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 10263dd5c225SAndreas Gohr * @param string $wikiName indentifier (shortcut) for the remote wiki 10273dd5c225SAndreas Gohr * @param string $wikiUri the fragment parsed from the original link 1028122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10290c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10300cecf9d5Sandi */ 1031de369923SAndreas Gohr public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { 1032b625487dSandi global $conf; 10330cecf9d5Sandi 103497a3e4e3Sandi $link = array(); 103597a3e4e3Sandi $link['target'] = $conf['target']['interwiki']; 103697a3e4e3Sandi $link['pre'] = ''; 103797a3e4e3Sandi $link['suf'] = ''; 10385e163278SAndreas Gohr $link['more'] = ''; 1039433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 1040914045f3SAndreas Gohr $link['rel'] = ''; 10410cecf9d5Sandi 104297a3e4e3Sandi //get interwiki URL 10436496c33fSGerrit Uitslag $exists = null; 10446496c33fSGerrit Uitslag $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 10450cecf9d5Sandi 104697a3e4e3Sandi if(!$isImage) { 10479d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 10489d2ddea4SAndreas Gohr $link['class'] = "interwiki iw_$class"; 10491c2d1019SAndreas Gohr } else { 10501c2d1019SAndreas Gohr $link['class'] = 'media'; 105197a3e4e3Sandi } 10520cecf9d5Sandi 105397a3e4e3Sandi //do we stay at the same server? Use local target 10542345e871SGerrit Uitslag if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 105597a3e4e3Sandi $link['target'] = $conf['target']['wiki']; 105697a3e4e3Sandi } 10576496c33fSGerrit Uitslag if($exists !== null && !$isImage) { 10586496c33fSGerrit Uitslag if($exists) { 10596496c33fSGerrit Uitslag $link['class'] .= ' wikilink1'; 10606496c33fSGerrit Uitslag } else { 10616496c33fSGerrit Uitslag $link['class'] .= ' wikilink2'; 1062914045f3SAndreas Gohr $link['rel'] .= ' nofollow'; 10636496c33fSGerrit Uitslag } 10646496c33fSGerrit Uitslag } 1065914045f3SAndreas Gohr if($conf['target']['interwiki']) $link['rel'] .= ' noopener'; 10660cecf9d5Sandi 106797a3e4e3Sandi $link['url'] = $url; 1068f7711f2bSAndreas Gohr $link['title'] = $this->_xmlEntities($link['url']); 106997a3e4e3Sandi 107097a3e4e3Sandi // output formatted 1071122f2d46SAndreas Böhler if($returnonly) { 1072abde5980SPhy if($url == '') return $link['name']; 1073122f2d46SAndreas Böhler return $this->_formatLink($link); 1074122f2d46SAndreas Böhler } else { 1075abde5980SPhy if($url == '') $this->doc .= $link['name']; 1076abde5980SPhy else $this->doc .= $this->_formatLink($link); 10770cecf9d5Sandi } 1078122f2d46SAndreas Böhler } 10790cecf9d5Sandi 10800cecf9d5Sandi /** 10813dd5c225SAndreas Gohr * Link to windows share 10823dd5c225SAndreas Gohr * 10833dd5c225SAndreas Gohr * @param string $url the link 10843dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1085122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10860c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10870cecf9d5Sandi */ 1088de369923SAndreas Gohr public function windowssharelink($url, $name = null, $returnonly = false) { 10891d47afe1Sandi global $conf; 10903dd5c225SAndreas Gohr 10911d47afe1Sandi //simple setup 109259bc3b48SGerrit Uitslag $link = array(); 10931d47afe1Sandi $link['target'] = $conf['target']['windows']; 10941d47afe1Sandi $link['pre'] = ''; 10951d47afe1Sandi $link['suf'] = ''; 10961d47afe1Sandi $link['style'] = ''; 10970cecf9d5Sandi 1098433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 10990cecf9d5Sandi if(!$isImage) { 11001d47afe1Sandi $link['class'] = 'windows'; 11010cecf9d5Sandi } else { 11021d47afe1Sandi $link['class'] = 'media'; 11030cecf9d5Sandi } 11040cecf9d5Sandi 1105433bef32Sandi $link['title'] = $this->_xmlEntities($url); 11061d47afe1Sandi $url = str_replace('\\', '/', $url); 11071d47afe1Sandi $url = 'file:///'.$url; 11081d47afe1Sandi $link['url'] = $url; 11090cecf9d5Sandi 11101d47afe1Sandi //output formatted 1111122f2d46SAndreas Böhler if($returnonly) { 1112122f2d46SAndreas Böhler return $this->_formatLink($link); 1113122f2d46SAndreas Böhler } else { 1114a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11150cecf9d5Sandi } 1116122f2d46SAndreas Böhler } 11170cecf9d5Sandi 11183dd5c225SAndreas Gohr /** 11193dd5c225SAndreas Gohr * Render a linked E-Mail Address 11203dd5c225SAndreas Gohr * 11213dd5c225SAndreas Gohr * Honors $conf['mailguard'] setting 11223dd5c225SAndreas Gohr * 11233dd5c225SAndreas Gohr * @param string $address Email-Address 11243dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1125122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 11260c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 11273dd5c225SAndreas Gohr */ 1128de369923SAndreas Gohr public function emaillink($address, $name = null, $returnonly = false) { 112971352defSandi global $conf; 113071352defSandi //simple setup 113171352defSandi $link = array(); 113271352defSandi $link['target'] = ''; 113371352defSandi $link['pre'] = ''; 113471352defSandi $link['suf'] = ''; 113571352defSandi $link['style'] = ''; 113671352defSandi $link['more'] = ''; 11370cecf9d5Sandi 1138c078fc55SAndreas Gohr $name = $this->_getLinkTitle($name, '', $isImage); 11390cecf9d5Sandi if(!$isImage) { 1140be96545cSAnika Henke $link['class'] = 'mail'; 11410cecf9d5Sandi } else { 1142be96545cSAnika Henke $link['class'] = 'media'; 11430cecf9d5Sandi } 11440cecf9d5Sandi 114507738714SAndreas Gohr $address = $this->_xmlEntities($address); 114600a7b5adSEsther Brunner $address = obfuscate($address); 114700a7b5adSEsther Brunner $title = $address; 11488c128049SAndreas Gohr 114971352defSandi if(empty($name)) { 115000a7b5adSEsther Brunner $name = $address; 115171352defSandi } 11520cecf9d5Sandi 1153776b36ecSAndreas Gohr if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1154776b36ecSAndreas Gohr 1155776b36ecSAndreas Gohr $link['url'] = 'mailto:'.$address; 115671352defSandi $link['name'] = $name; 115771352defSandi $link['title'] = $title; 11580cecf9d5Sandi 115971352defSandi //output formatted 1160122f2d46SAndreas Böhler if($returnonly) { 1161122f2d46SAndreas Böhler return $this->_formatLink($link); 1162122f2d46SAndreas Böhler } else { 1163a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11640cecf9d5Sandi } 1165122f2d46SAndreas Böhler } 11660cecf9d5Sandi 11673dd5c225SAndreas Gohr /** 11683dd5c225SAndreas Gohr * Render an internal media file 11693dd5c225SAndreas Gohr * 11703dd5c225SAndreas Gohr * @param string $src media ID 11713dd5c225SAndreas Gohr * @param string $title descriptive text 11723dd5c225SAndreas Gohr * @param string $align left|center|right 11733dd5c225SAndreas Gohr * @param int $width width of media in pixel 11743dd5c225SAndreas Gohr * @param int $height height of media in pixel 11753dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11763dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 11773dd5c225SAndreas Gohr * @param bool $return return HTML instead of adding to $doc 11780c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11793dd5c225SAndreas Gohr */ 1180de369923SAndreas Gohr public function internalmedia($src, $title = null, $align = null, $width = null, 11813dd5c225SAndreas Gohr $height = null, $cache = null, $linking = null, $return = false) { 118237e34a5eSandi global $ID; 11838f34cf3dSMichael Große if (strpos($src, '#') !== false) { 1184*ec34bb30SAndreas Gohr list($src, $hash) = sexplode('#', $src, 2); 11858f34cf3dSMichael Große } 11868c6be208SAndreas Gohr $src = (new MediaResolver($ID))->resolveId($src,$this->date_at,true); 11878c6be208SAndreas Gohr $exists = media_exists($src); 11880cecf9d5Sandi 1189d98d4540SBen Coburn $noLink = false; 11908acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1191b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 11923685f775Sandi 11933dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1194b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 119564159a61SAndreas Gohr $link['url'] = ml( 119664159a61SAndreas Gohr $src, 119764159a61SAndreas Gohr array( 119864159a61SAndreas Gohr 'id' => $ID, 119964159a61SAndreas Gohr 'cache' => $cache, 120064159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 120164159a61SAndreas Gohr ), 120264159a61SAndreas Gohr ($linking == 'direct') 120364159a61SAndreas Gohr ); 1204f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12052a2a2ba2SAnika Henke // don't link movies 120644881bd0Shenning.noren $noLink = true; 120755efc227SAndreas Gohr } else { 12082ca14335SEsther Brunner // add file icons 12099d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 12109d2ddea4SAndreas Gohr $link['class'] .= ' mediafile mf_'.$class; 121164159a61SAndreas Gohr $link['url'] = ml( 121264159a61SAndreas Gohr $src, 121364159a61SAndreas Gohr array( 121464159a61SAndreas Gohr 'id' => $ID, 121564159a61SAndreas Gohr 'cache' => $cache, 121664159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 121764159a61SAndreas Gohr ), 121864159a61SAndreas Gohr true 121964159a61SAndreas Gohr ); 122091328684SMichael Hamann if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; 122155efc227SAndreas Gohr } 12223685f775Sandi 12238f34cf3dSMichael Große if (!empty($hash)) $link['url'] .= '#'.$hash; 122491df343aSAndreas Gohr 12256fe20453SGina Haeussge //markup non existing files 12264a24b459SKate Arzamastseva if(!$exists) { 12276fe20453SGina Haeussge $link['class'] .= ' wikilink2'; 12284a24b459SKate Arzamastseva } 12296fe20453SGina Haeussge 12303685f775Sandi //output formatted 1231f50634f0SAnika Henke if($return) { 1232f50634f0SAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1233f50634f0SAnika Henke else return $this->_formatLink($link); 1234f50634f0SAnika Henke } else { 1235dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 12362ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 12370cecf9d5Sandi } 1238f50634f0SAnika Henke } 12390cecf9d5Sandi 12403dd5c225SAndreas Gohr /** 12413dd5c225SAndreas Gohr * Render an external media file 12423dd5c225SAndreas Gohr * 12433dd5c225SAndreas Gohr * @param string $src full media URL 12443dd5c225SAndreas Gohr * @param string $title descriptive text 12453dd5c225SAndreas Gohr * @param string $align left|center|right 12463dd5c225SAndreas Gohr * @param int $width width of media in pixel 12473dd5c225SAndreas Gohr * @param int $height height of media in pixel 12483dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 12493dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 1250410ee62aSAnika Henke * @param bool $return return HTML instead of adding to $doc 12510c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 12523dd5c225SAndreas Gohr */ 1253de369923SAndreas Gohr public function externalmedia($src, $title = null, $align = null, $width = null, 1254410ee62aSAnika Henke $height = null, $cache = null, $linking = null, $return = false) { 12556efc45a2SDmitry Katsubo if(link_isinterwiki($src)){ 1256*ec34bb30SAndreas Gohr list($shortcut, $reference) = sexplode('>', $src, 2, ''); 12576efc45a2SDmitry Katsubo $exists = null; 12586efc45a2SDmitry Katsubo $src = $this->_resolveInterWiki($shortcut, $reference, $exists); 1259abde5980SPhy if($src == '' && empty($title)){ 1260abde5980SPhy // make sure at least something will be shown in this case 1261abde5980SPhy $title = $reference; 1262abde5980SPhy } 12636efc45a2SDmitry Katsubo } 1264*ec34bb30SAndreas Gohr list($src, $hash) = sexplode('#', $src, 2); 1265d98d4540SBen Coburn $noLink = false; 1266abde5980SPhy if($src == '') { 1267abde5980SPhy // only output plaintext without link if there is no src 1268abde5980SPhy $noLink = true; 1269abde5980SPhy } 12708acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1271b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1272b739ff0fSPierre Spring 1273b739ff0fSPierre Spring $link['url'] = ml($src, array('cache' => $cache)); 12743685f775Sandi 12753dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1276b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 12772ca14335SEsther Brunner // link only jpeg images 127844881bd0Shenning.noren // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1279f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12802a2a2ba2SAnika Henke // don't link movies 128144881bd0Shenning.noren $noLink = true; 12822ca14335SEsther Brunner } else { 12832ca14335SEsther Brunner // add file icons 128427bf7924STom N Harris $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 128527bf7924STom N Harris $link['class'] .= ' mediafile mf_'.$class; 12862ca14335SEsther Brunner } 12872ca14335SEsther Brunner 128891df343aSAndreas Gohr if($hash) $link['url'] .= '#'.$hash; 128991df343aSAndreas Gohr 12903685f775Sandi //output formatted 1291410ee62aSAnika Henke if($return) { 1292410ee62aSAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1293410ee62aSAnika Henke else return $this->_formatLink($link); 1294410ee62aSAnika Henke } else { 1295dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 12962ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 12970cecf9d5Sandi } 1298410ee62aSAnika Henke } 12990cecf9d5Sandi 13004826ab45Sandi /** 13013db95becSAndreas Gohr * Renders an RSS feed 1302b625487dSandi * 13030c4c0281SGerrit Uitslag * @param string $url URL of the feed 13040c4c0281SGerrit Uitslag * @param array $params Finetuning of the output 13050c4c0281SGerrit Uitslag * 1306b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 1307b625487dSandi */ 1308de369923SAndreas Gohr public function rss($url, $params) { 1309b625487dSandi global $lang; 13103db95becSAndreas Gohr global $conf; 13113db95becSAndreas Gohr 13123db95becSAndreas Gohr require_once(DOKU_INC.'inc/FeedParser.php'); 13133db95becSAndreas Gohr $feed = new FeedParser(); 131400077af8SAndreas Gohr $feed->set_feed_url($url); 1315b625487dSandi 1316b625487dSandi //disable warning while fetching 13173dd5c225SAndreas Gohr if(!defined('DOKU_E_LEVEL')) { 13183dd5c225SAndreas Gohr $elvl = error_reporting(E_ERROR); 13193dd5c225SAndreas Gohr } 13203db95becSAndreas Gohr $rc = $feed->init(); 13213dd5c225SAndreas Gohr if(isset($elvl)) { 13223dd5c225SAndreas Gohr error_reporting($elvl); 13233dd5c225SAndreas Gohr } 1324b625487dSandi 132538c6f603SRobin H. Johnson if($params['nosort']) $feed->enable_order_by_date(false); 132638c6f603SRobin H. Johnson 13273db95becSAndreas Gohr //decide on start and end 13283db95becSAndreas Gohr if($params['reverse']) { 13293db95becSAndreas Gohr $mod = -1; 13303db95becSAndreas Gohr $start = $feed->get_item_quantity() - 1; 13313db95becSAndreas Gohr $end = $start - ($params['max']); 1332b2a412b0SAndreas Gohr $end = ($end < -1) ? -1 : $end; 13333db95becSAndreas Gohr } else { 13343db95becSAndreas Gohr $mod = 1; 13353db95becSAndreas Gohr $start = 0; 13363db95becSAndreas Gohr $end = $feed->get_item_quantity(); 1337d91ab76fSMatt Perry $end = ($end > $params['max']) ? $params['max'] : $end; 13383db95becSAndreas Gohr } 13393db95becSAndreas Gohr 1340a2d649c4Sandi $this->doc .= '<ul class="rss">'; 13413db95becSAndreas Gohr if($rc) { 13423db95becSAndreas Gohr for($x = $start; $x != $end; $x += $mod) { 13431bde1582SAndreas Gohr $item = $feed->get_item($x); 13443db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1345d2ea3363SAndreas Gohr // support feeds without links 1346d2ea3363SAndreas Gohr $lnkurl = $item->get_permalink(); 1347d2ea3363SAndreas Gohr if($lnkurl) { 1348793361f8SAndreas Gohr // title is escaped by SimplePie, we unescape here because it 1349793361f8SAndreas Gohr // is escaped again in externallink() FS#1705 13503dd5c225SAndreas Gohr $this->externallink( 13513dd5c225SAndreas Gohr $item->get_permalink(), 13523dd5c225SAndreas Gohr html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') 13533dd5c225SAndreas Gohr ); 1354d2ea3363SAndreas Gohr } else { 1355d2ea3363SAndreas Gohr $this->doc .= ' '.$item->get_title(); 1356d2ea3363SAndreas Gohr } 13573db95becSAndreas Gohr if($params['author']) { 13581bde1582SAndreas Gohr $author = $item->get_author(0); 13591bde1582SAndreas Gohr if($author) { 13601bde1582SAndreas Gohr $name = $author->get_name(); 13611bde1582SAndreas Gohr if(!$name) $name = $author->get_email(); 1362163c2842SPhy if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name); 13631bde1582SAndreas Gohr } 13643db95becSAndreas Gohr } 13653db95becSAndreas Gohr if($params['date']) { 13662e7e0c29SAndreas Gohr $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 13673db95becSAndreas Gohr } 13681bde1582SAndreas Gohr if($params['details']) { 13693db95becSAndreas Gohr $this->doc .= '<div class="detail">'; 1370173dccb7STom N Harris if($conf['htmlok']) { 13711bde1582SAndreas Gohr $this->doc .= $item->get_description(); 13723db95becSAndreas Gohr } else { 13731bde1582SAndreas Gohr $this->doc .= strip_tags($item->get_description()); 13743db95becSAndreas Gohr } 13753db95becSAndreas Gohr $this->doc .= '</div>'; 13763db95becSAndreas Gohr } 13773db95becSAndreas Gohr 13783db95becSAndreas Gohr $this->doc .= '</div></li>'; 1379b625487dSandi } 1380b625487dSandi } else { 13813db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1382a2d649c4Sandi $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 1383b625487dSandi $this->externallink($url); 138445e147ccSAndreas Gohr if($conf['allowdebug']) { 138545e147ccSAndreas Gohr $this->doc .= '<!--'.hsc($feed->error).'-->'; 138645e147ccSAndreas Gohr } 13873db95becSAndreas Gohr $this->doc .= '</div></li>'; 1388b625487dSandi } 1389a2d649c4Sandi $this->doc .= '</ul>'; 1390b625487dSandi } 1391b625487dSandi 13923dd5c225SAndreas Gohr /** 13933dd5c225SAndreas Gohr * Start a table 13943dd5c225SAndreas Gohr * 13953dd5c225SAndreas Gohr * @param int $maxcols maximum number of columns 13963dd5c225SAndreas Gohr * @param int $numrows NOT IMPLEMENTED 13973dd5c225SAndreas Gohr * @param int $pos byte position in the original source 13987d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 13993dd5c225SAndreas Gohr */ 1400de369923SAndreas Gohr public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { 1401b5742cedSPierre Spring // initialize the row counter used for classes 1402b5742cedSPierre Spring $this->_counter['row_counter'] = 0; 1403619736fdSAdrian Lang $class = 'table'; 14040c4c0281SGerrit Uitslag if($classes !== null) { 14052e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14060c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14070c4c0281SGerrit Uitslag } 1408619736fdSAdrian Lang if($pos !== null) { 140906917fceSMichael Große $hid = $this->_headerToLink($class, true); 1410ec57f119SLarsDW223 $data = array(); 1411ec57f119SLarsDW223 $data['target'] = 'table'; 1412ec57f119SLarsDW223 $data['name'] = ''; 1413ec57f119SLarsDW223 $data['hid'] = $hid; 1414ec57f119SLarsDW223 $class .= ' '.$this->startSectionEdit($pos, $data); 1415619736fdSAdrian Lang } 1416619736fdSAdrian Lang $this->doc .= '<div class="'.$class.'"><table class="inline">'. 1417619736fdSAdrian Lang DOKU_LF; 14180cecf9d5Sandi } 14190cecf9d5Sandi 14203dd5c225SAndreas Gohr /** 14213dd5c225SAndreas Gohr * Close a table 14223dd5c225SAndreas Gohr * 14233dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14243dd5c225SAndreas Gohr */ 1425de369923SAndreas Gohr public function table_close($pos = null) { 1426a8574918SAnika Henke $this->doc .= '</table></div>'.DOKU_LF; 1427619736fdSAdrian Lang if($pos !== null) { 142890df9a4dSAdrian Lang $this->finishSectionEdit($pos); 14290cecf9d5Sandi } 1430619736fdSAdrian Lang } 14310cecf9d5Sandi 14323dd5c225SAndreas Gohr /** 14333dd5c225SAndreas Gohr * Open a table header 14343dd5c225SAndreas Gohr */ 1435de369923SAndreas Gohr public function tablethead_open() { 1436f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 1437f05a1cc5SGerrit Uitslag } 1438f05a1cc5SGerrit Uitslag 14393dd5c225SAndreas Gohr /** 14403dd5c225SAndreas Gohr * Close a table header 14413dd5c225SAndreas Gohr */ 1442de369923SAndreas Gohr public function tablethead_close() { 1443f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 1444f05a1cc5SGerrit Uitslag } 1445f05a1cc5SGerrit Uitslag 14463dd5c225SAndreas Gohr /** 14475a93f869SAnika Henke * Open a table body 14485a93f869SAnika Henke */ 1449de369923SAndreas Gohr public function tabletbody_open() { 14505a93f869SAnika Henke $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF; 14515a93f869SAnika Henke } 14525a93f869SAnika Henke 14535a93f869SAnika Henke /** 14545a93f869SAnika Henke * Close a table body 14555a93f869SAnika Henke */ 1456de369923SAndreas Gohr public function tabletbody_close() { 14575a93f869SAnika Henke $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF; 14585a93f869SAnika Henke } 14595a93f869SAnika Henke 14605a93f869SAnika Henke /** 1461d2a99739SAndreas Gohr * Open a table footer 1462d2a99739SAndreas Gohr */ 1463de369923SAndreas Gohr public function tabletfoot_open() { 146444f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF; 1465d2a99739SAndreas Gohr } 1466d2a99739SAndreas Gohr 1467d2a99739SAndreas Gohr /** 1468d2a99739SAndreas Gohr * Close a table footer 1469d2a99739SAndreas Gohr */ 1470de369923SAndreas Gohr public function tabletfoot_close() { 147144f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF; 1472d2a99739SAndreas Gohr } 1473d2a99739SAndreas Gohr 1474d2a99739SAndreas Gohr /** 14753dd5c225SAndreas Gohr * Open a table row 14760c4c0281SGerrit Uitslag * 14777d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14783dd5c225SAndreas Gohr */ 1479de369923SAndreas Gohr public function tablerow_open($classes = null) { 1480b5742cedSPierre Spring // initialize the cell counter used for classes 1481b5742cedSPierre Spring $this->_counter['cell_counter'] = 0; 1482b5742cedSPierre Spring $class = 'row'.$this->_counter['row_counter']++; 14830c4c0281SGerrit Uitslag if($classes !== null) { 14842e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14850c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14860c4c0281SGerrit Uitslag } 1487b5742cedSPierre Spring $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; 14880cecf9d5Sandi } 14890cecf9d5Sandi 14903dd5c225SAndreas Gohr /** 14913dd5c225SAndreas Gohr * Close a table row 14923dd5c225SAndreas Gohr */ 1493de369923SAndreas Gohr public function tablerow_close() { 1494a2d649c4Sandi $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 14950cecf9d5Sandi } 14960cecf9d5Sandi 14973dd5c225SAndreas Gohr /** 14983dd5c225SAndreas Gohr * Open a table header cell 14993dd5c225SAndreas Gohr * 15003dd5c225SAndreas Gohr * @param int $colspan 15013dd5c225SAndreas Gohr * @param string $align left|center|right 15023dd5c225SAndreas Gohr * @param int $rowspan 15037d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15043dd5c225SAndreas Gohr */ 1505de369923SAndreas Gohr public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1506b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 15070cecf9d5Sandi if(!is_null($align)) { 1508b5742cedSPierre Spring $class .= ' '.$align.'align'; 15090cecf9d5Sandi } 15100c4c0281SGerrit Uitslag if($classes !== null) { 15112e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 15120c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15130c4c0281SGerrit Uitslag } 1514b5742cedSPierre Spring $class .= '"'; 1515b5742cedSPierre Spring $this->doc .= '<th '.$class; 15160cecf9d5Sandi if($colspan > 1) { 1517a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1518a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 15190cecf9d5Sandi } 152025b97867Shakan.sandell if($rowspan > 1) { 152125b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 152225b97867Shakan.sandell } 1523a2d649c4Sandi $this->doc .= '>'; 15240cecf9d5Sandi } 15250cecf9d5Sandi 15263dd5c225SAndreas Gohr /** 15273dd5c225SAndreas Gohr * Close a table header cell 15283dd5c225SAndreas Gohr */ 1529de369923SAndreas Gohr public function tableheader_close() { 1530a2d649c4Sandi $this->doc .= '</th>'; 15310cecf9d5Sandi } 15320cecf9d5Sandi 15333dd5c225SAndreas Gohr /** 15343dd5c225SAndreas Gohr * Open a table cell 15353dd5c225SAndreas Gohr * 15363dd5c225SAndreas Gohr * @param int $colspan 15373dd5c225SAndreas Gohr * @param string $align left|center|right 15383dd5c225SAndreas Gohr * @param int $rowspan 15397d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15403dd5c225SAndreas Gohr */ 1541de369923SAndreas Gohr public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1542b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 15430cecf9d5Sandi if(!is_null($align)) { 1544b5742cedSPierre Spring $class .= ' '.$align.'align'; 15450cecf9d5Sandi } 15460c4c0281SGerrit Uitslag if($classes !== null) { 15472e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 15480c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15490c4c0281SGerrit Uitslag } 1550b5742cedSPierre Spring $class .= '"'; 1551b5742cedSPierre Spring $this->doc .= '<td '.$class; 15520cecf9d5Sandi if($colspan > 1) { 1553a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1554a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 15550cecf9d5Sandi } 155625b97867Shakan.sandell if($rowspan > 1) { 155725b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 155825b97867Shakan.sandell } 1559a2d649c4Sandi $this->doc .= '>'; 15600cecf9d5Sandi } 15610cecf9d5Sandi 15623dd5c225SAndreas Gohr /** 15633dd5c225SAndreas Gohr * Close a table cell 15643dd5c225SAndreas Gohr */ 1565de369923SAndreas Gohr public function tablecell_close() { 1566a2d649c4Sandi $this->doc .= '</td>'; 15670cecf9d5Sandi } 15680cecf9d5Sandi 1569cea664bdSLarsDW223 /** 1570cea664bdSLarsDW223 * Returns the current header level. 1571cea664bdSLarsDW223 * (required e.g. by the filelist plugin) 1572cea664bdSLarsDW223 * 1573cea664bdSLarsDW223 * @return int The current header level 1574cea664bdSLarsDW223 */ 1575de369923SAndreas Gohr public function getLastlevel() { 1576cea664bdSLarsDW223 return $this->lastlevel; 1577cea664bdSLarsDW223 } 1578cea664bdSLarsDW223 15793dd5c225SAndreas Gohr #region Utility functions 15800cecf9d5Sandi 1581ba11bd29Sandi /** 15823fd0b676Sandi * Build a link 15833fd0b676Sandi * 15843fd0b676Sandi * Assembles all parts defined in $link returns HTML for the link 1585ba11bd29Sandi * 15860c4c0281SGerrit Uitslag * @param array $link attributes of a link 15870c4c0281SGerrit Uitslag * @return string 15880c4c0281SGerrit Uitslag * 1589ba11bd29Sandi * @author Andreas Gohr <andi@splitbrain.org> 1590ba11bd29Sandi */ 1591de369923SAndreas Gohr public function _formatLink($link) { 1592ba11bd29Sandi //make sure the url is XHTML compliant (skip mailto) 1593ba11bd29Sandi if(substr($link['url'], 0, 7) != 'mailto:') { 1594ba11bd29Sandi $link['url'] = str_replace('&', '&', $link['url']); 1595ba11bd29Sandi $link['url'] = str_replace('&amp;', '&', $link['url']); 1596ba11bd29Sandi } 1597ba11bd29Sandi //remove double encodings in titles 1598ba11bd29Sandi $link['title'] = str_replace('&amp;', '&', $link['title']); 1599ba11bd29Sandi 1600453493f2SAndreas Gohr // be sure there are no bad chars in url or title 1601453493f2SAndreas Gohr // (we can't do this for name because it can contain an img tag) 1602453493f2SAndreas Gohr $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); 1603453493f2SAndreas Gohr $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); 1604453493f2SAndreas Gohr 1605ba11bd29Sandi $ret = ''; 1606ba11bd29Sandi $ret .= $link['pre']; 1607ba11bd29Sandi $ret .= '<a href="'.$link['url'].'"'; 1608bb4866bdSchris if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 1609bb4866bdSchris if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 1610bb4866bdSchris if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 1611bb4866bdSchris if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 1612914045f3SAndreas Gohr if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"'; 1613bb4866bdSchris if(!empty($link['more'])) $ret .= ' '.$link['more']; 1614ba11bd29Sandi $ret .= '>'; 1615ba11bd29Sandi $ret .= $link['name']; 1616ba11bd29Sandi $ret .= '</a>'; 1617ba11bd29Sandi $ret .= $link['suf']; 1618ba11bd29Sandi return $ret; 1619ba11bd29Sandi } 1620ba11bd29Sandi 1621ba11bd29Sandi /** 16223fd0b676Sandi * Renders internal and external media 16233fd0b676Sandi * 16243fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 16253dd5c225SAndreas Gohr * @param string $src media ID 16263dd5c225SAndreas Gohr * @param string $title descriptive text 16273dd5c225SAndreas Gohr * @param string $align left|center|right 16283dd5c225SAndreas Gohr * @param int $width width of media in pixel 16293dd5c225SAndreas Gohr * @param int $height height of media in pixel 16303dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 16313dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 16323dd5c225SAndreas Gohr * @return string 16333fd0b676Sandi */ 1634de369923SAndreas Gohr public function _media($src, $title = null, $align = null, $width = null, 16350ea51e63SMatt Perry $height = null, $cache = null, $render = true) { 16363fd0b676Sandi 16373fd0b676Sandi $ret = ''; 16383fd0b676Sandi 16393dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src); 16403fd0b676Sandi if(substr($mime, 0, 5) == 'image') { 1641b739ff0fSPierre Spring // first get the $title 1642b739ff0fSPierre Spring if(!is_null($title)) { 1643b739ff0fSPierre Spring $title = $this->_xmlEntities($title); 1644b739ff0fSPierre Spring } elseif($ext == 'jpg' || $ext == 'jpeg') { 1645b739ff0fSPierre Spring //try to use the caption from IPTC/EXIF 1646b739ff0fSPierre Spring require_once(DOKU_INC.'inc/JpegMeta.php'); 164767f9913dSAndreas Gohr $jpeg = new JpegMeta(mediaFN($src)); 1648b739ff0fSPierre Spring if($jpeg !== false) $cap = $jpeg->getTitle(); 16493dd5c225SAndreas Gohr if(!empty($cap)) { 1650b739ff0fSPierre Spring $title = $this->_xmlEntities($cap); 1651b739ff0fSPierre Spring } 1652b739ff0fSPierre Spring } 1653b739ff0fSPierre Spring if(!$render) { 1654b739ff0fSPierre Spring // if the picture is not supposed to be rendered 1655b739ff0fSPierre Spring // return the title of the picture 1656768be5a3SPhy if($title === null || $title === "") { 1657b739ff0fSPierre Spring // just show the sourcename 16588cbc5ee8SAndreas Gohr $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 1659b739ff0fSPierre Spring } 1660b739ff0fSPierre Spring return $title; 1661b739ff0fSPierre Spring } 16623fd0b676Sandi //add image tag 166364159a61SAndreas Gohr $ret .= '<img src="' . ml( 166464159a61SAndreas Gohr $src, 166564159a61SAndreas Gohr array( 166664159a61SAndreas Gohr 'w' => $width, 'h' => $height, 166764159a61SAndreas Gohr 'cache' => $cache, 166864159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 166964159a61SAndreas Gohr ) 167064159a61SAndreas Gohr ) . '"'; 16713fd0b676Sandi $ret .= ' class="media'.$align.'"'; 16724732b197SAndreas Gohr $ret .= ' loading="lazy"'; 16733fd0b676Sandi 1674b739ff0fSPierre Spring if($title) { 1675b739ff0fSPierre Spring $ret .= ' title="'.$title.'"'; 1676b739ff0fSPierre Spring $ret .= ' alt="'.$title.'"'; 16773fd0b676Sandi } else { 16783fd0b676Sandi $ret .= ' alt=""'; 16793fd0b676Sandi } 16803fd0b676Sandi 16813fd0b676Sandi if(!is_null($width)) 16823fd0b676Sandi $ret .= ' width="'.$this->_xmlEntities($width).'"'; 16833fd0b676Sandi 16843fd0b676Sandi if(!is_null($height)) 16853fd0b676Sandi $ret .= ' height="'.$this->_xmlEntities($height).'"'; 16863fd0b676Sandi 16873fd0b676Sandi $ret .= ' />'; 16883fd0b676Sandi 168917954bb5SAnika Henke } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 16902a2a2ba2SAnika Henke // first get the $title 1691a8dcb874Sbleistivt $title = !is_null($title) ? $title : false; 16922a2a2ba2SAnika Henke if(!$render) { 169317954bb5SAnika Henke // if the file is not supposed to be rendered 169417954bb5SAnika Henke // return the title of the file (just the sourcename if there is no title) 1695a8dcb874Sbleistivt return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src))); 16962a2a2ba2SAnika Henke } 16972a2a2ba2SAnika Henke 16982a2a2ba2SAnika Henke $att = array(); 16992a2a2ba2SAnika Henke $att['class'] = "media$align"; 170017954bb5SAnika Henke if($title) { 170117954bb5SAnika Henke $att['title'] = $title; 170217954bb5SAnika Henke } 17032a2a2ba2SAnika Henke 170417954bb5SAnika Henke if(media_supportedav($mime, 'video')) { 170517954bb5SAnika Henke //add video 170679e53fe5SAnika Henke $ret .= $this->_video($src, $width, $height, $att); 1707b44a5dceSAnika Henke } 170817954bb5SAnika Henke if(media_supportedav($mime, 'audio')) { 1709b44a5dceSAnika Henke //add audio 1710b44a5dceSAnika Henke $ret .= $this->_audio($src, $att); 171117954bb5SAnika Henke } 1712b44a5dceSAnika Henke 17133fd0b676Sandi } elseif($mime == 'application/x-shockwave-flash') { 17141c882ba8SAndreas Gohr if(!$render) { 17151c882ba8SAndreas Gohr // if the flash is not supposed to be rendered 17161c882ba8SAndreas Gohr // return the title of the flash 17171c882ba8SAndreas Gohr if(!$title) { 17181c882ba8SAndreas Gohr // just show the sourcename 17198cbc5ee8SAndreas Gohr $title = \dokuwiki\Utf8\PhpString::basename(noNS($src)); 17201c882ba8SAndreas Gohr } 172107bf32b2SAndreas Gohr return $this->_xmlEntities($title); 17221c882ba8SAndreas Gohr } 17231c882ba8SAndreas Gohr 172407bf32b2SAndreas Gohr $att = array(); 172507bf32b2SAndreas Gohr $att['class'] = "media$align"; 172607bf32b2SAndreas Gohr if($align == 'right') $att['align'] = 'right'; 172707bf32b2SAndreas Gohr if($align == 'left') $att['align'] = 'left'; 17283dd5c225SAndreas Gohr $ret .= html_flashobject( 17293dd5c225SAndreas Gohr ml($src, array('cache' => $cache), true, '&'), $width, $height, 173007bf32b2SAndreas Gohr array('quality' => 'high'), 173107bf32b2SAndreas Gohr null, 173207bf32b2SAndreas Gohr $att, 17333dd5c225SAndreas Gohr $this->_xmlEntities($title) 17343dd5c225SAndreas Gohr ); 17350f428d7dSAndreas Gohr } elseif($title) { 17363fd0b676Sandi // well at least we have a title to display 17373fd0b676Sandi $ret .= $this->_xmlEntities($title); 17383fd0b676Sandi } else { 17395291ca3aSAndreas Gohr // just show the sourcename 17408cbc5ee8SAndreas Gohr $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 17413fd0b676Sandi } 17423fd0b676Sandi 17433fd0b676Sandi return $ret; 17443fd0b676Sandi } 17453fd0b676Sandi 17463dd5c225SAndreas Gohr /** 17473dd5c225SAndreas Gohr * Escape string for output 17483dd5c225SAndreas Gohr * 17493dd5c225SAndreas Gohr * @param $string 17503dd5c225SAndreas Gohr * @return string 17513dd5c225SAndreas Gohr */ 1752de369923SAndreas Gohr public function _xmlEntities($string) { 1753f7711f2bSAndreas Gohr return hsc($string); 17540cecf9d5Sandi } 17550cecf9d5Sandi 1756de369923SAndreas Gohr 17570cecf9d5Sandi 1758af587fa8Sandi /** 17593fd0b676Sandi * Construct a title and handle images in titles 17603fd0b676Sandi * 17610b7c14c2Sandi * @author Harry Fuecks <hfuecks@gmail.com> 17623dd5c225SAndreas Gohr * @param string|array $title either string title or media array 17633dd5c225SAndreas Gohr * @param string $default default title if nothing else is found 17643dd5c225SAndreas Gohr * @param bool $isImage will be set to true if it's a media file 17653dd5c225SAndreas Gohr * @param null|string $id linked page id (used to extract title from first heading) 17663dd5c225SAndreas Gohr * @param string $linktype content|navigation 17673dd5c225SAndreas Gohr * @return string HTML of the title, might be full image tag or just escaped text 17683fd0b676Sandi */ 1769de369923SAndreas Gohr public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { 177044881bd0Shenning.noren $isImage = false; 177129657f9eSAndreas Gohr if(is_array($title)) { 177229657f9eSAndreas Gohr $isImage = true; 177329657f9eSAndreas Gohr return $this->_imageTitle($title); 177429657f9eSAndreas Gohr } elseif(is_null($title) || trim($title) == '') { 1775fe9ec250SChris Smith if(useHeading($linktype) && $id) { 177667c15eceSMichael Hamann $heading = p_get_first_heading($id); 1777f515db7fSAndreas Gohr if(!blank($heading)) { 1778433bef32Sandi return $this->_xmlEntities($heading); 1779bb0a59d4Sjan } 1780bb0a59d4Sjan } 1781433bef32Sandi return $this->_xmlEntities($default); 178268c26e6dSMichael Klier } else { 178368c26e6dSMichael Klier return $this->_xmlEntities($title); 17840cecf9d5Sandi } 17850cecf9d5Sandi } 17860cecf9d5Sandi 17870cecf9d5Sandi /** 17883dd5c225SAndreas Gohr * Returns HTML code for images used in link titles 17893fd0b676Sandi * 17903fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 1791e0c26282SGerrit Uitslag * @param array $img 17923dd5c225SAndreas Gohr * @return string HTML img tag or similar 17930cecf9d5Sandi */ 1794de369923SAndreas Gohr public function _imageTitle($img) { 1795d9baf1a7SKazutaka Miyasaka global $ID; 1796d9baf1a7SKazutaka Miyasaka 1797d9baf1a7SKazutaka Miyasaka // some fixes on $img['src'] 1798d9baf1a7SKazutaka Miyasaka // see internalmedia() and externalmedia() 17993dd5c225SAndreas Gohr list($img['src']) = explode('#', $img['src'], 2); 1800d9baf1a7SKazutaka Miyasaka if($img['type'] == 'internalmedia') { 18018c6be208SAndreas Gohr $img['src'] = (new MediaResolver($ID))->resolveId($img['src'], $this->date_at, true); 1802d9baf1a7SKazutaka Miyasaka } 1803d9baf1a7SKazutaka Miyasaka 18043dd5c225SAndreas Gohr return $this->_media( 18053dd5c225SAndreas Gohr $img['src'], 18064826ab45Sandi $img['title'], 18074826ab45Sandi $img['align'], 18084826ab45Sandi $img['width'], 18094826ab45Sandi $img['height'], 18103dd5c225SAndreas Gohr $img['cache'] 18113dd5c225SAndreas Gohr ); 18120cecf9d5Sandi } 1813b739ff0fSPierre Spring 1814b739ff0fSPierre Spring /** 18153dd5c225SAndreas Gohr * helperfunction to return a basic link to a media 18163dd5c225SAndreas Gohr * 18173dd5c225SAndreas Gohr * used in internalmedia() and externalmedia() 1818b739ff0fSPierre Spring * 1819b739ff0fSPierre Spring * @author Pierre Spring <pierre.spring@liip.ch> 18203dd5c225SAndreas Gohr * @param string $src media ID 18213dd5c225SAndreas Gohr * @param string $title descriptive text 18223dd5c225SAndreas Gohr * @param string $align left|center|right 18233dd5c225SAndreas Gohr * @param int $width width of media in pixel 18243dd5c225SAndreas Gohr * @param int $height height of media in pixel 18253dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 18263dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 18273dd5c225SAndreas Gohr * @return array associative array with link config 1828b739ff0fSPierre Spring */ 1829de369923SAndreas Gohr public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { 1830b739ff0fSPierre Spring global $conf; 1831b739ff0fSPierre Spring 1832b739ff0fSPierre Spring $link = array(); 1833b739ff0fSPierre Spring $link['class'] = 'media'; 1834b739ff0fSPierre Spring $link['style'] = ''; 1835b739ff0fSPierre Spring $link['pre'] = ''; 1836b739ff0fSPierre Spring $link['suf'] = ''; 1837b739ff0fSPierre Spring $link['more'] = ''; 1838b739ff0fSPierre Spring $link['target'] = $conf['target']['media']; 1839bc3d2252SAndreas Gohr if($conf['target']['media']) $link['rel'] = 'noopener'; 1840b739ff0fSPierre Spring $link['title'] = $this->_xmlEntities($src); 1841b739ff0fSPierre Spring $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1842b739ff0fSPierre Spring 1843b739ff0fSPierre Spring return $link; 1844b739ff0fSPierre Spring } 184591459163SAnika Henke 18462a2a2ba2SAnika Henke /** 18472a2a2ba2SAnika Henke * Embed video(s) in HTML 18482a2a2ba2SAnika Henke * 18492a2a2ba2SAnika Henke * @author Anika Henke <anika@selfthinker.org> 18500877a1f1SSchplurtz le Déboulonné * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 18512a2a2ba2SAnika Henke * 18522a2a2ba2SAnika Henke * @param string $src - ID of video to embed 18532a2a2ba2SAnika Henke * @param int $width - width of the video in pixels 18542a2a2ba2SAnika Henke * @param int $height - height of the video in pixels 18552a2a2ba2SAnika Henke * @param array $atts - additional attributes for the <video> tag 1856f50634f0SAnika Henke * @return string 18572a2a2ba2SAnika Henke */ 1858de369923SAndreas Gohr public function _video($src, $width, $height, $atts = null) { 18592a2a2ba2SAnika Henke // prepare width and height 18602a2a2ba2SAnika Henke if(is_null($atts)) $atts = array(); 18612a2a2ba2SAnika Henke $atts['width'] = (int) $width; 18622a2a2ba2SAnika Henke $atts['height'] = (int) $height; 18632a2a2ba2SAnika Henke if(!$atts['width']) $atts['width'] = 320; 18642a2a2ba2SAnika Henke if(!$atts['height']) $atts['height'] = 240; 18652a2a2ba2SAnika Henke 1866410ee62aSAnika Henke $posterUrl = ''; 1867410ee62aSAnika Henke $files = array(); 18680877a1f1SSchplurtz le Déboulonné $tracks = array(); 1869410ee62aSAnika Henke $isExternal = media_isexternal($src); 1870410ee62aSAnika Henke 1871410ee62aSAnika Henke if ($isExternal) { 1872410ee62aSAnika Henke // take direct source for external files 1873702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1874410ee62aSAnika Henke $files[$srcMime] = $src; 1875410ee62aSAnika Henke } else { 18763d7a9e0aSAnika Henke // prepare alternative formats 18773d7a9e0aSAnika Henke $extensions = array('webm', 'ogv', 'mp4'); 1878410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 187959bc3b48SGerrit Uitslag $poster = media_alternativefiles($src, array('jpg', 'png')); 18800877a1f1SSchplurtz le Déboulonné $tracks = media_trackfiles($src); 188199f943f6SAnika Henke if(!empty($poster)) { 18822d338eabSAndreas Gohr $posterUrl = ml(reset($poster), '', true, '&'); 188399f943f6SAnika Henke } 1884410ee62aSAnika Henke } 18852a2a2ba2SAnika Henke 1886f50634f0SAnika Henke $out = ''; 188779e53fe5SAnika Henke // open video tag 1888f50634f0SAnika Henke $out .= '<video '.buildAttributes($atts).' controls="controls"'; 18893641199aSAnika Henke if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; 1890f50634f0SAnika Henke $out .= '>'.NL; 18913641199aSAnika Henke $fallback = ''; 189279e53fe5SAnika Henke 189379e53fe5SAnika Henke // output source for each alternative video format 1894410ee62aSAnika Henke foreach($files as $mime => $file) { 1895410ee62aSAnika Henke if ($isExternal) { 1896410ee62aSAnika Henke $url = $file; 1897410ee62aSAnika Henke $linkType = 'externalmedia'; 1898410ee62aSAnika Henke } else { 18992d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1900410ee62aSAnika Henke $linkType = 'internalmedia'; 1901410ee62aSAnika Henke } 1902056bf31fSDamien Regad $title = !empty($atts['title']) 1903056bf31fSDamien Regad ? $atts['title'] 1904056bf31fSDamien Regad : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 19053d7a9e0aSAnika Henke 1906f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 190779e53fe5SAnika Henke // alternative content (just a link to the file) 190864159a61SAndreas Gohr $fallback .= $this->$linkType( 190964159a61SAndreas Gohr $file, 191064159a61SAndreas Gohr $title, 191164159a61SAndreas Gohr null, 191264159a61SAndreas Gohr null, 191364159a61SAndreas Gohr null, 191464159a61SAndreas Gohr $cache = null, 191564159a61SAndreas Gohr $linking = 'linkonly', 191664159a61SAndreas Gohr $return = true 191764159a61SAndreas Gohr ); 19183d7a9e0aSAnika Henke } 19192a2a2ba2SAnika Henke 19200877a1f1SSchplurtz le Déboulonné // output each track if any 19210877a1f1SSchplurtz le Déboulonné foreach( $tracks as $trackid => $info ) { 192223c61bbeSSchplurtz le Déboulonné list( $kind, $srclang ) = array_map( 'hsc', $info ); 192323c61bbeSSchplurtz le Déboulonné $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; 192423c61bbeSSchplurtz le Déboulonné $out .= "label=\"$srclang\" "; 19250877a1f1SSchplurtz le Déboulonné $out .= 'src="'.ml($trackid, '', true).'">'.NL; 19260877a1f1SSchplurtz le Déboulonné } 19270877a1f1SSchplurtz le Déboulonné 19282a2a2ba2SAnika Henke // finish 19293641199aSAnika Henke $out .= $fallback; 1930f50634f0SAnika Henke $out .= '</video>'.NL; 1931f50634f0SAnika Henke return $out; 19322a2a2ba2SAnika Henke } 19332a2a2ba2SAnika Henke 1934b44a5dceSAnika Henke /** 1935b44a5dceSAnika Henke * Embed audio in HTML 1936b44a5dceSAnika Henke * 1937b44a5dceSAnika Henke * @author Anika Henke <anika@selfthinker.org> 1938b44a5dceSAnika Henke * 1939b44a5dceSAnika Henke * @param string $src - ID of audio to embed 19406d4af72aSAnika Henke * @param array $atts - additional attributes for the <audio> tag 1941f50634f0SAnika Henke * @return string 1942b44a5dceSAnika Henke */ 1943de369923SAndreas Gohr public function _audio($src, $atts = array()) { 1944702e97d3SAnika Henke $files = array(); 1945410ee62aSAnika Henke $isExternal = media_isexternal($src); 1946b44a5dceSAnika Henke 1947410ee62aSAnika Henke if ($isExternal) { 1948410ee62aSAnika Henke // take direct source for external files 1949702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1950410ee62aSAnika Henke $files[$srcMime] = $src; 1951410ee62aSAnika Henke } else { 1952b44a5dceSAnika Henke // prepare alternative formats 1953b44a5dceSAnika Henke $extensions = array('ogg', 'mp3', 'wav'); 1954410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 1955410ee62aSAnika Henke } 1956b44a5dceSAnika Henke 1957f50634f0SAnika Henke $out = ''; 1958b44a5dceSAnika Henke // open audio tag 1959f50634f0SAnika Henke $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; 19603641199aSAnika Henke $fallback = ''; 1961b44a5dceSAnika Henke 1962b44a5dceSAnika Henke // output source for each alternative audio format 1963410ee62aSAnika Henke foreach($files as $mime => $file) { 1964410ee62aSAnika Henke if ($isExternal) { 1965410ee62aSAnika Henke $url = $file; 1966410ee62aSAnika Henke $linkType = 'externalmedia'; 1967410ee62aSAnika Henke } else { 19682d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1969410ee62aSAnika Henke $linkType = 'internalmedia'; 1970410ee62aSAnika Henke } 19718cbc5ee8SAndreas Gohr $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 1972b44a5dceSAnika Henke 1973f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1974b44a5dceSAnika Henke // alternative content (just a link to the file) 197564159a61SAndreas Gohr $fallback .= $this->$linkType( 197664159a61SAndreas Gohr $file, 197764159a61SAndreas Gohr $title, 197864159a61SAndreas Gohr null, 197964159a61SAndreas Gohr null, 198064159a61SAndreas Gohr null, 198164159a61SAndreas Gohr $cache = null, 198264159a61SAndreas Gohr $linking = 'linkonly', 198364159a61SAndreas Gohr $return = true 198464159a61SAndreas Gohr ); 1985b44a5dceSAnika Henke } 1986b44a5dceSAnika Henke 1987b44a5dceSAnika Henke // finish 19883641199aSAnika Henke $out .= $fallback; 1989f50634f0SAnika Henke $out .= '</audio>'.NL; 1990f50634f0SAnika Henke return $out; 1991b44a5dceSAnika Henke } 1992b44a5dceSAnika Henke 19935c2eed9aSlisps /** 199452dc5eadSlisps * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 19955c2eed9aSlisps * which returns an existing media revision less or equal to rev or date_at 19965c2eed9aSlisps * 19975c2eed9aSlisps * @author lisps 19985c2eed9aSlisps * @param string $media_id 19995c2eed9aSlisps * @access protected 20005c2eed9aSlisps * @return string revision ('' for current) 20015c2eed9aSlisps */ 2002de369923SAndreas Gohr protected function _getLastMediaRevisionAt($media_id) { 200352dc5eadSlisps if (!$this->date_at || media_isexternal($media_id)) return ''; 2004252acce3SSatoshi Sahara $changelog = new MediaChangeLog($media_id); 2005252acce3SSatoshi Sahara return $changelog->getLastRevisionAt($this->date_at); 20065c2eed9aSlisps } 20075c2eed9aSlisps 20083dd5c225SAndreas Gohr #endregion 20090cecf9d5Sandi} 20100cecf9d5Sandi 2011e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 2012