10cecf9d5Sandi<?php 20c3a5702SAndreas Gohr 30c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 4*db926724SAndreas Gohruse dokuwiki\Feed\FeedParser; 52cd6cc0aSAndreas Gohruse dokuwiki\File\MediaResolver; 62cd6cc0aSAndreas Gohruse dokuwiki\File\PageResolver; 7faf3f01bSAndreas Gohruse dokuwiki\Utf8\PhpString; 8faf3f01bSAndreas Gohruse SimplePie\Author; 90c3a5702SAndreas Gohr 10b625487dSandi/** 11b625487dSandi * Renderer for XHTML output 12b625487dSandi * 13b4f2363aSAndreas Gohr * This is DokuWiki's main renderer used to display page content in the wiki 14b4f2363aSAndreas Gohr * 15b625487dSandi * @author Harry Fuecks <hfuecks@gmail.com> 16b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 173dd5c225SAndreas Gohr * 180cecf9d5Sandi */ 19faf3f01bSAndreas Gohrclass Doku_Renderer_xhtml extends Doku_Renderer 20faf3f01bSAndreas Gohr{ 213dd5c225SAndreas Gohr /** @var array store the table of contents */ 22faf3f01bSAndreas Gohr public $toc = []; 230cecf9d5Sandi 243dd5c225SAndreas Gohr /** @var array A stack of section edit data */ 25faf3f01bSAndreas Gohr protected $sectionedits = []; 26de369923SAndreas Gohr 273dd5c225SAndreas Gohr /** @var int last section edit id, used by startSectionEdit */ 283dd5c225SAndreas Gohr protected $lastsecid = 0; 290cecf9d5Sandi 3016ec3e37SAndreas Gohr /** @var array a list of footnotes, list starts at 1! */ 31faf3f01bSAndreas Gohr protected $footnotes = []; 327764a90aSandi 333dd5c225SAndreas Gohr /** @var int current section level */ 343dd5c225SAndreas Gohr protected $lastlevel = 0; 353dd5c225SAndreas Gohr /** @var array section node tracker */ 36faf3f01bSAndreas Gohr protected $node = [0, 0, 0, 0, 0]; 373dd5c225SAndreas Gohr 383dd5c225SAndreas Gohr /** @var string temporary $doc store */ 393dd5c225SAndreas Gohr protected $store = ''; 403dd5c225SAndreas Gohr 413dd5c225SAndreas Gohr /** @var array global counter, for table classes etc. */ 42faf3f01bSAndreas Gohr protected $_counter = []; // 433dd5c225SAndreas Gohr 443dd5c225SAndreas Gohr /** @var int counts the code and file blocks, used to provide download links */ 453dd5c225SAndreas Gohr protected $_codeblock = 0; 463dd5c225SAndreas Gohr 473dd5c225SAndreas Gohr /** @var array list of allowed URL schemes */ 48faf3f01bSAndreas Gohr protected $schemes; 49b5742cedSPierre Spring 5090df9a4dSAdrian Lang /** 5190df9a4dSAdrian Lang * Register a new edit section range 5290df9a4dSAdrian Lang * 5342ea7f44SGerrit Uitslag * @param int $start The byte position for the edit start 54ec57f119SLarsDW223 * @param array $data Associative array with section data: 55ec57f119SLarsDW223 * Key 'name': the section name/title 56ec57f119SLarsDW223 * Key 'target': the target for the section edit, 57ec57f119SLarsDW223 * e.g. 'section' or 'table' 58ec57f119SLarsDW223 * Key 'hid': header id 59ec57f119SLarsDW223 * Key 'codeblockOffset': actual code block index 60ec57f119SLarsDW223 * Key 'start': set in startSectionEdit(), 61ec57f119SLarsDW223 * do not set yourself 62ec57f119SLarsDW223 * Key 'range': calculated from 'start' and 63ec57f119SLarsDW223 * $key in finishSectionEdit(), 64ec57f119SLarsDW223 * do not set yourself 6590df9a4dSAdrian Lang * @return string A marker class for the starting HTML element 6642ea7f44SGerrit Uitslag * 6790df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 6890df9a4dSAdrian Lang */ 69faf3f01bSAndreas Gohr public function startSectionEdit($start, $data) 70faf3f01bSAndreas Gohr { 71ec57f119SLarsDW223 if (!is_array($data)) { 72ac025fdfSAndreas Gohr msg( 73ac025fdfSAndreas Gohr sprintf( 74ac025fdfSAndreas Gohr 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', 75ac025fdfSAndreas Gohr hsc((string)$data) 7695078f23SAndreas Gohr ), 7795078f23SAndreas Gohr -1 78ac025fdfSAndreas Gohr ); 79ac025fdfSAndreas Gohr 80ac025fdfSAndreas Gohr // @deprecated 2018-04-14, backward compatibility 81ac025fdfSAndreas Gohr $args = func_get_args(); 82faf3f01bSAndreas Gohr $data = []; 83ac025fdfSAndreas Gohr if (isset($args[1])) $data['target'] = $args[1]; 84ac025fdfSAndreas Gohr if (isset($args[2])) $data['name'] = $args[2]; 85ac025fdfSAndreas Gohr if (isset($args[3])) $data['hid'] = $args[3]; 86ec57f119SLarsDW223 } 87ec57f119SLarsDW223 $data['secid'] = ++$this->lastsecid; 88ec57f119SLarsDW223 $data['start'] = $start; 89ec57f119SLarsDW223 $this->sectionedits[] = $data; 90ec57f119SLarsDW223 return 'sectionedit' . $data['secid']; 9190df9a4dSAdrian Lang } 9290df9a4dSAdrian Lang 9390df9a4dSAdrian Lang /** 9490df9a4dSAdrian Lang * Finish an edit section range 9590df9a4dSAdrian Lang * 9642ea7f44SGerrit Uitslag * @param int $end The byte position for the edit end; null for the rest of the page 9742ea7f44SGerrit Uitslag * 9890df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 9990df9a4dSAdrian Lang */ 100faf3f01bSAndreas Gohr public function finishSectionEdit($end = null, $hid = null) 101faf3f01bSAndreas Gohr { 102ad43fdbfSasivery if (count($this->sectionedits) == 0) { 1030d9f02ecSasivery return; 1040d9f02ecSasivery } 105ec57f119SLarsDW223 $data = array_pop($this->sectionedits); 106ec57f119SLarsDW223 if (!is_null($end) && $end <= $data['start']) { 10700c13053SAdrian Lang return; 10800c13053SAdrian Lang } 1092571786cSLarsDW223 if (!is_null($hid)) { 110ec57f119SLarsDW223 $data['hid'] .= $hid; 1112571786cSLarsDW223 } 112ec57f119SLarsDW223 $data['range'] = $data['start'] . '-' . (is_null($end) ? '' : $end); 113ec57f119SLarsDW223 unset($data['start']); 114faf3f01bSAndreas Gohr $this->doc .= '<!-- EDIT' . hsc(json_encode($data, JSON_THROW_ON_ERROR)) . ' -->'; 11590df9a4dSAdrian Lang } 11690df9a4dSAdrian Lang 1173dd5c225SAndreas Gohr /** 1183dd5c225SAndreas Gohr * Returns the format produced by this renderer. 1193dd5c225SAndreas Gohr * 1203dd5c225SAndreas Gohr * @return string always 'xhtml' 1213dd5c225SAndreas Gohr */ 122faf3f01bSAndreas Gohr public function getFormat() 123faf3f01bSAndreas Gohr { 1245f70445dSAndreas Gohr return 'xhtml'; 1255f70445dSAndreas Gohr } 1265f70445dSAndreas Gohr 1273dd5c225SAndreas Gohr /** 1283dd5c225SAndreas Gohr * Initialize the document 1293dd5c225SAndreas Gohr */ 130faf3f01bSAndreas Gohr public function document_start() 131faf3f01bSAndreas Gohr { 132c5a8fd96SAndreas Gohr //reset some internals 133faf3f01bSAndreas Gohr $this->toc = []; 1340cecf9d5Sandi } 1350cecf9d5Sandi 1363dd5c225SAndreas Gohr /** 1373dd5c225SAndreas Gohr * Finalize the document 1383dd5c225SAndreas Gohr */ 139faf3f01bSAndreas Gohr public function document_end() 140faf3f01bSAndreas Gohr { 14190df9a4dSAdrian Lang // Finish open section edits. 142faf3f01bSAndreas Gohr while ($this->sectionedits !== []) { 143ec57f119SLarsDW223 if ($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { 14490df9a4dSAdrian Lang // If there is only one section, do not write a section edit 14590df9a4dSAdrian Lang // marker. 14690df9a4dSAdrian Lang array_pop($this->sectionedits); 14790df9a4dSAdrian Lang } else { 148d9e36cbeSAdrian Lang $this->finishSectionEdit(); 14990df9a4dSAdrian Lang } 15090df9a4dSAdrian Lang } 15190df9a4dSAdrian Lang 152faf3f01bSAndreas Gohr if ($this->footnotes !== []) { 153a2d649c4Sandi $this->doc .= '<div class="footnotes">' . DOKU_LF; 154d74aace9Schris 15516ec3e37SAndreas Gohr foreach ($this->footnotes as $id => $footnote) { 156d74aace9Schris // check its not a placeholder that indicates actual footnote text is elsewhere 1576c16a3a9Sfiwswe if (!str_starts_with($footnote, "@@FNT")) { 158d74aace9Schris // open the footnote and set the anchor and backlink 159d74aace9Schris $this->doc .= '<div class="fn">'; 16016cc7ed7SAnika Henke $this->doc .= '<sup><a href="#fnt__' . $id . '" id="fn__' . $id . '" class="fn_bot">'; 16129bfcd16SAndreas Gohr $this->doc .= $id . ')</a></sup> ' . DOKU_LF; 162d74aace9Schris 163d74aace9Schris // get any other footnotes that use the same markup 164d74aace9Schris $alt = array_keys($this->footnotes, "@@FNT$id"); 165d74aace9Schris 166d74aace9Schris foreach ($alt as $ref) { 167d74aace9Schris // set anchor and backlink for the other footnotes 16816ec3e37SAndreas Gohr $this->doc .= ', <sup><a href="#fnt__' . ($ref) . '" id="fn__' . ($ref) . '" class="fn_bot">'; 16916ec3e37SAndreas Gohr $this->doc .= ($ref) . ')</a></sup> ' . DOKU_LF; 170d74aace9Schris } 171d74aace9Schris 172d74aace9Schris // add footnote markup and close this footnote 173694afa06SAnika Henke $this->doc .= '<div class="content">' . $footnote . '</div>'; 174d74aace9Schris $this->doc .= '</div>' . DOKU_LF; 175d74aace9Schris } 1760cecf9d5Sandi } 177a2d649c4Sandi $this->doc .= '</div>' . DOKU_LF; 1780cecf9d5Sandi } 179c5a8fd96SAndreas Gohr 180b8595a66SAndreas Gohr // Prepare the TOC 181851f2e89SAnika Henke global $conf; 18264159a61SAndreas Gohr if ( 18364159a61SAndreas Gohr $this->info['toc'] && 18464159a61SAndreas Gohr is_array($this->toc) && 18564159a61SAndreas Gohr $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] 18664159a61SAndreas Gohr ) { 187b8595a66SAndreas Gohr global $TOC; 188b8595a66SAndreas Gohr $TOC = $this->toc; 1890cecf9d5Sandi } 1903e55d035SAndreas Gohr 1913e55d035SAndreas Gohr // make sure there are no empty paragraphs 19227918226Schris $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 193e41c4da9SAndreas Gohr } 1940cecf9d5Sandi 1953dd5c225SAndreas Gohr /** 1963dd5c225SAndreas Gohr * Add an item to the TOC 1973dd5c225SAndreas Gohr * 1983dd5c225SAndreas Gohr * @param string $id the hash link 1993dd5c225SAndreas Gohr * @param string $text the text to display 2003dd5c225SAndreas Gohr * @param int $level the nesting level 2013dd5c225SAndreas Gohr */ 202faf3f01bSAndreas Gohr public function toc_additem($id, $text, $level) 203faf3f01bSAndreas Gohr { 204af587fa8Sandi global $conf; 205af587fa8Sandi 206c5a8fd96SAndreas Gohr //handle TOC 207c5a8fd96SAndreas Gohr if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 2087d91652aSAndreas Gohr $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 209c5a8fd96SAndreas Gohr } 210e7856beaSchris } 211e7856beaSchris 2123dd5c225SAndreas Gohr /** 2133dd5c225SAndreas Gohr * Render a heading 2143dd5c225SAndreas Gohr * 2153dd5c225SAndreas Gohr * @param string $text the text to display 2163dd5c225SAndreas Gohr * @param int $level header level 2173dd5c225SAndreas Gohr * @param int $pos byte position in the original source 218e3c00e6eSIain Hallam * @param bool $returnonly whether to return html or write to doc attribute 219e3c00e6eSIain Hallam * @return void|string writes to doc attribute or returns html depends on $returnonly 2203dd5c225SAndreas Gohr */ 221faf3f01bSAndreas Gohr public function header($text, $level, $pos, $returnonly = false) 222faf3f01bSAndreas Gohr { 22390df9a4dSAdrian Lang global $conf; 22490df9a4dSAdrian Lang 225f515db7fSAndreas Gohr if (blank($text)) return; //skip empty headlines 226e7856beaSchris 227e7856beaSchris $hid = $this->_headerToLink($text, true); 228e7856beaSchris 229e7856beaSchris //only add items within configured levels 230e7856beaSchris $this->toc_additem($hid, $text, $level); 231c5a8fd96SAndreas Gohr 23291459163SAnika Henke // adjust $node to reflect hierarchy of levels 23391459163SAnika Henke $this->node[$level - 1]++; 23491459163SAnika Henke if ($level < $this->lastlevel) { 23591459163SAnika Henke for ($i = 0; $i < $this->lastlevel - $level; $i++) { 23691459163SAnika Henke $this->node[$this->lastlevel - $i - 1] = 0; 23791459163SAnika Henke } 23891459163SAnika Henke } 23991459163SAnika Henke $this->lastlevel = $level; 24091459163SAnika Henke 24195078f23SAndreas Gohr if ( 24295078f23SAndreas Gohr $level <= $conf['maxseclevel'] && 243faf3f01bSAndreas Gohr $this->sectionedits !== [] && 244ec57f119SLarsDW223 $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' 2453dd5c225SAndreas Gohr ) { 2466c1f778cSAdrian Lang $this->finishSectionEdit($pos - 1); 24790df9a4dSAdrian Lang } 24890df9a4dSAdrian Lang 249e3c00e6eSIain Hallam // build the header 250e3c00e6eSIain Hallam $header = DOKU_LF . '<h' . $level; 25190df9a4dSAdrian Lang if ($level <= $conf['maxseclevel']) { 252faf3f01bSAndreas Gohr $data = []; 253ec57f119SLarsDW223 $data['target'] = 'section'; 254ec57f119SLarsDW223 $data['name'] = $text; 255ec57f119SLarsDW223 $data['hid'] = $hid; 256ec57f119SLarsDW223 $data['codeblockOffset'] = $this->_codeblock; 257e3c00e6eSIain Hallam $header .= ' class="' . $this->startSectionEdit($pos, $data) . '"'; 25890df9a4dSAdrian Lang } 259e3c00e6eSIain Hallam $header .= ' id="' . $hid . '">'; 260e3c00e6eSIain Hallam $header .= $this->_xmlEntities($text); 261e3c00e6eSIain Hallam $header .= "</h$level>" . DOKU_LF; 262e3c00e6eSIain Hallam 263e3c00e6eSIain Hallam if ($returnonly) { 264e3c00e6eSIain Hallam return $header; 265e3c00e6eSIain Hallam } else { 266e3c00e6eSIain Hallam $this->doc .= $header; 267e3c00e6eSIain Hallam } 2680cecf9d5Sandi } 2690cecf9d5Sandi 2703dd5c225SAndreas Gohr /** 2713dd5c225SAndreas Gohr * Open a new section 2723dd5c225SAndreas Gohr * 2733dd5c225SAndreas Gohr * @param int $level section level (as determined by the previous header) 2743dd5c225SAndreas Gohr */ 275faf3f01bSAndreas Gohr public function section_open($level) 276faf3f01bSAndreas Gohr { 2779864e7b1SAdrian Lang $this->doc .= '<div class="level' . $level . '">' . DOKU_LF; 2780cecf9d5Sandi } 2790cecf9d5Sandi 2803dd5c225SAndreas Gohr /** 2813dd5c225SAndreas Gohr * Close the current section 2823dd5c225SAndreas Gohr */ 283faf3f01bSAndreas Gohr public function section_close() 284faf3f01bSAndreas Gohr { 285a2d649c4Sandi $this->doc .= DOKU_LF . '</div>' . DOKU_LF; 2860cecf9d5Sandi } 2870cecf9d5Sandi 2883dd5c225SAndreas Gohr /** 2893dd5c225SAndreas Gohr * Render plain text data 2903dd5c225SAndreas Gohr * 2913dd5c225SAndreas Gohr * @param $text 2923dd5c225SAndreas Gohr */ 293faf3f01bSAndreas Gohr public function cdata($text) 294faf3f01bSAndreas Gohr { 295a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 2960cecf9d5Sandi } 2970cecf9d5Sandi 2983dd5c225SAndreas Gohr /** 2993dd5c225SAndreas Gohr * Open a paragraph 3003dd5c225SAndreas Gohr */ 301faf3f01bSAndreas Gohr public function p_open() 302faf3f01bSAndreas Gohr { 30359869a4bSAnika Henke $this->doc .= DOKU_LF . '<p>' . DOKU_LF; 3040cecf9d5Sandi } 3050cecf9d5Sandi 3063dd5c225SAndreas Gohr /** 3073dd5c225SAndreas Gohr * Close a paragraph 3083dd5c225SAndreas Gohr */ 309faf3f01bSAndreas Gohr public function p_close() 310faf3f01bSAndreas Gohr { 31159869a4bSAnika Henke $this->doc .= DOKU_LF . '</p>' . DOKU_LF; 3120cecf9d5Sandi } 3130cecf9d5Sandi 3143dd5c225SAndreas Gohr /** 3153dd5c225SAndreas Gohr * Create a line break 3163dd5c225SAndreas Gohr */ 317faf3f01bSAndreas Gohr public function linebreak() 318faf3f01bSAndreas Gohr { 319a2d649c4Sandi $this->doc .= '<br/>' . DOKU_LF; 3200cecf9d5Sandi } 3210cecf9d5Sandi 3223dd5c225SAndreas Gohr /** 3233dd5c225SAndreas Gohr * Create a horizontal line 3243dd5c225SAndreas Gohr */ 325faf3f01bSAndreas Gohr public function hr() 326faf3f01bSAndreas Gohr { 3274beabca9SAnika Henke $this->doc .= '<hr />' . DOKU_LF; 3280cecf9d5Sandi } 3290cecf9d5Sandi 3303dd5c225SAndreas Gohr /** 3313dd5c225SAndreas Gohr * Start strong (bold) formatting 3323dd5c225SAndreas Gohr */ 333faf3f01bSAndreas Gohr public function strong_open() 334faf3f01bSAndreas Gohr { 335a2d649c4Sandi $this->doc .= '<strong>'; 3360cecf9d5Sandi } 3370cecf9d5Sandi 3383dd5c225SAndreas Gohr /** 3393dd5c225SAndreas Gohr * Stop strong (bold) formatting 3403dd5c225SAndreas Gohr */ 341faf3f01bSAndreas Gohr public function strong_close() 342faf3f01bSAndreas Gohr { 343a2d649c4Sandi $this->doc .= '</strong>'; 3440cecf9d5Sandi } 3450cecf9d5Sandi 3463dd5c225SAndreas Gohr /** 3473dd5c225SAndreas Gohr * Start emphasis (italics) formatting 3483dd5c225SAndreas Gohr */ 349faf3f01bSAndreas Gohr public function emphasis_open() 350faf3f01bSAndreas Gohr { 351a2d649c4Sandi $this->doc .= '<em>'; 3520cecf9d5Sandi } 3530cecf9d5Sandi 3543dd5c225SAndreas Gohr /** 3553dd5c225SAndreas Gohr * Stop emphasis (italics) formatting 3563dd5c225SAndreas Gohr */ 357faf3f01bSAndreas Gohr public function emphasis_close() 358faf3f01bSAndreas Gohr { 359a2d649c4Sandi $this->doc .= '</em>'; 3600cecf9d5Sandi } 3610cecf9d5Sandi 3623dd5c225SAndreas Gohr /** 3633dd5c225SAndreas Gohr * Start underline formatting 3643dd5c225SAndreas Gohr */ 365faf3f01bSAndreas Gohr public function underline_open() 366faf3f01bSAndreas Gohr { 36702e51121SAnika Henke $this->doc .= '<em class="u">'; 3680cecf9d5Sandi } 3690cecf9d5Sandi 3703dd5c225SAndreas Gohr /** 3713dd5c225SAndreas Gohr * Stop underline formatting 3723dd5c225SAndreas Gohr */ 373faf3f01bSAndreas Gohr public function underline_close() 374faf3f01bSAndreas Gohr { 37502e51121SAnika Henke $this->doc .= '</em>'; 3760cecf9d5Sandi } 3770cecf9d5Sandi 3783dd5c225SAndreas Gohr /** 3793dd5c225SAndreas Gohr * Start monospace formatting 3803dd5c225SAndreas Gohr */ 381faf3f01bSAndreas Gohr public function monospace_open() 382faf3f01bSAndreas Gohr { 383a2d649c4Sandi $this->doc .= '<code>'; 3840cecf9d5Sandi } 3850cecf9d5Sandi 3863dd5c225SAndreas Gohr /** 3873dd5c225SAndreas Gohr * Stop monospace formatting 3883dd5c225SAndreas Gohr */ 389faf3f01bSAndreas Gohr public function monospace_close() 390faf3f01bSAndreas Gohr { 391a2d649c4Sandi $this->doc .= '</code>'; 3920cecf9d5Sandi } 3930cecf9d5Sandi 3943dd5c225SAndreas Gohr /** 3953dd5c225SAndreas Gohr * Start a subscript 3963dd5c225SAndreas Gohr */ 397faf3f01bSAndreas Gohr public function subscript_open() 398faf3f01bSAndreas Gohr { 399a2d649c4Sandi $this->doc .= '<sub>'; 4000cecf9d5Sandi } 4010cecf9d5Sandi 4023dd5c225SAndreas Gohr /** 4033dd5c225SAndreas Gohr * Stop a subscript 4043dd5c225SAndreas Gohr */ 405faf3f01bSAndreas Gohr public function subscript_close() 406faf3f01bSAndreas Gohr { 407a2d649c4Sandi $this->doc .= '</sub>'; 4080cecf9d5Sandi } 4090cecf9d5Sandi 4103dd5c225SAndreas Gohr /** 4113dd5c225SAndreas Gohr * Start a superscript 4123dd5c225SAndreas Gohr */ 413faf3f01bSAndreas Gohr public function superscript_open() 414faf3f01bSAndreas Gohr { 415a2d649c4Sandi $this->doc .= '<sup>'; 4160cecf9d5Sandi } 4170cecf9d5Sandi 4183dd5c225SAndreas Gohr /** 4193dd5c225SAndreas Gohr * Stop a superscript 4203dd5c225SAndreas Gohr */ 421faf3f01bSAndreas Gohr public function superscript_close() 422faf3f01bSAndreas Gohr { 423a2d649c4Sandi $this->doc .= '</sup>'; 4240cecf9d5Sandi } 4250cecf9d5Sandi 4263dd5c225SAndreas Gohr /** 4273dd5c225SAndreas Gohr * Start deleted (strike-through) formatting 4283dd5c225SAndreas Gohr */ 429faf3f01bSAndreas Gohr public function deleted_open() 430faf3f01bSAndreas Gohr { 431a2d649c4Sandi $this->doc .= '<del>'; 4320cecf9d5Sandi } 4330cecf9d5Sandi 4343dd5c225SAndreas Gohr /** 4353dd5c225SAndreas Gohr * Stop deleted (strike-through) formatting 4363dd5c225SAndreas Gohr */ 437faf3f01bSAndreas Gohr public function deleted_close() 438faf3f01bSAndreas Gohr { 439a2d649c4Sandi $this->doc .= '</del>'; 4400cecf9d5Sandi } 4410cecf9d5Sandi 4423fd0b676Sandi /** 4433fd0b676Sandi * Callback for footnote start syntax 4443fd0b676Sandi * 4453fd0b676Sandi * All following content will go to the footnote instead of 446d74aace9Schris * the document. To achieve this the previous rendered content 4473fd0b676Sandi * is moved to $store and $doc is cleared 4483fd0b676Sandi * 4493fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 4503fd0b676Sandi */ 451faf3f01bSAndreas Gohr public function footnote_open() 452faf3f01bSAndreas Gohr { 4537764a90aSandi 4547764a90aSandi // move current content to store and record footnote 4557764a90aSandi $this->store = $this->doc; 4567764a90aSandi $this->doc = ''; 4570cecf9d5Sandi } 4580cecf9d5Sandi 4593fd0b676Sandi /** 4603fd0b676Sandi * Callback for footnote end syntax 4613fd0b676Sandi * 4623fd0b676Sandi * All rendered content is moved to the $footnotes array and the old 4633fd0b676Sandi * content is restored from $store again 4643fd0b676Sandi * 4653fd0b676Sandi * @author Andreas Gohr 4663fd0b676Sandi */ 467faf3f01bSAndreas Gohr public function footnote_close() 468faf3f01bSAndreas Gohr { 46916ec3e37SAndreas Gohr /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 47016ec3e37SAndreas Gohr static $fnid = 0; 47116ec3e37SAndreas Gohr // assign new footnote id (we start at 1) 47216ec3e37SAndreas Gohr $fnid++; 4737764a90aSandi 474d74aace9Schris // recover footnote into the stack and restore old content 475d74aace9Schris $footnote = $this->doc; 4767764a90aSandi $this->doc = $this->store; 4777764a90aSandi $this->store = ''; 478d74aace9Schris 479d74aace9Schris // check to see if this footnote has been seen before 480d74aace9Schris $i = array_search($footnote, $this->footnotes); 481d74aace9Schris 482d74aace9Schris if ($i === false) { 483d74aace9Schris // its a new footnote, add it to the $footnotes array 48416ec3e37SAndreas Gohr $this->footnotes[$fnid] = $footnote; 485d74aace9Schris } else { 48616ec3e37SAndreas Gohr // seen this one before, save a placeholder 48716ec3e37SAndreas Gohr $this->footnotes[$fnid] = "@@FNT" . ($i); 488d74aace9Schris } 489d74aace9Schris 4906b379cbfSAndreas Gohr // output the footnote reference and link 49195078f23SAndreas Gohr $this->doc .= sprintf( 49295078f23SAndreas Gohr '<sup><a href="#fn__%d" id="fnt__%d" class="fn_top">%d)</a></sup>', 49395078f23SAndreas Gohr $fnid, 49495078f23SAndreas Gohr $fnid, 49595078f23SAndreas Gohr $fnid 49695078f23SAndreas Gohr ); 4970cecf9d5Sandi } 4980cecf9d5Sandi 4993dd5c225SAndreas Gohr /** 5003dd5c225SAndreas Gohr * Open an unordered list 5010c4c0281SGerrit Uitslag * 5027d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 5033dd5c225SAndreas Gohr */ 504faf3f01bSAndreas Gohr public function listu_open($classes = null) 505faf3f01bSAndreas Gohr { 5060c4c0281SGerrit Uitslag $class = ''; 5070c4c0281SGerrit Uitslag if ($classes !== null) { 508faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 5090c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 5100c4c0281SGerrit Uitslag } 5110c4c0281SGerrit Uitslag $this->doc .= "<ul$class>" . DOKU_LF; 5120cecf9d5Sandi } 5130cecf9d5Sandi 5143dd5c225SAndreas Gohr /** 5153dd5c225SAndreas Gohr * Close an unordered list 5163dd5c225SAndreas Gohr */ 517faf3f01bSAndreas Gohr public function listu_close() 518faf3f01bSAndreas Gohr { 519a2d649c4Sandi $this->doc .= '</ul>' . DOKU_LF; 5200cecf9d5Sandi } 5210cecf9d5Sandi 5223dd5c225SAndreas Gohr /** 5233dd5c225SAndreas Gohr * Open an ordered list 5240c4c0281SGerrit Uitslag * 5257d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 5263dd5c225SAndreas Gohr */ 527faf3f01bSAndreas Gohr public function listo_open($classes = null) 528faf3f01bSAndreas Gohr { 5290c4c0281SGerrit Uitslag $class = ''; 5300c4c0281SGerrit Uitslag if ($classes !== null) { 531faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 5320c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 5330c4c0281SGerrit Uitslag } 5340c4c0281SGerrit Uitslag $this->doc .= "<ol$class>" . DOKU_LF; 5350cecf9d5Sandi } 5360cecf9d5Sandi 5373dd5c225SAndreas Gohr /** 5383dd5c225SAndreas Gohr * Close an ordered list 5393dd5c225SAndreas Gohr */ 540faf3f01bSAndreas Gohr public function listo_close() 541faf3f01bSAndreas Gohr { 542a2d649c4Sandi $this->doc .= '</ol>' . DOKU_LF; 5430cecf9d5Sandi } 5440cecf9d5Sandi 5453dd5c225SAndreas Gohr /** 5463dd5c225SAndreas Gohr * Open a list item 5473dd5c225SAndreas Gohr * 5483dd5c225SAndreas Gohr * @param int $level the nesting level 549e3a24861SChristopher Smith * @param bool $node true when a node; false when a leaf 5503dd5c225SAndreas Gohr */ 551faf3f01bSAndreas Gohr public function listitem_open($level, $node = false) 552faf3f01bSAndreas Gohr { 553e3a24861SChristopher Smith $branching = $node ? ' node' : ''; 554e3a24861SChristopher Smith $this->doc .= '<li class="level' . $level . $branching . '">'; 5550cecf9d5Sandi } 5560cecf9d5Sandi 5573dd5c225SAndreas Gohr /** 5583dd5c225SAndreas Gohr * Close a list item 5593dd5c225SAndreas Gohr */ 560faf3f01bSAndreas Gohr public function listitem_close() 561faf3f01bSAndreas Gohr { 562a2d649c4Sandi $this->doc .= '</li>' . DOKU_LF; 5630cecf9d5Sandi } 5640cecf9d5Sandi 5653dd5c225SAndreas Gohr /** 5663dd5c225SAndreas Gohr * Start the content of a list item 5673dd5c225SAndreas Gohr */ 568faf3f01bSAndreas Gohr public function listcontent_open() 569faf3f01bSAndreas Gohr { 57090db23d7Schris $this->doc .= '<div class="li">'; 5710cecf9d5Sandi } 5720cecf9d5Sandi 5733dd5c225SAndreas Gohr /** 5743dd5c225SAndreas Gohr * Stop the content of a list item 5753dd5c225SAndreas Gohr */ 576faf3f01bSAndreas Gohr public function listcontent_close() 577faf3f01bSAndreas Gohr { 57859869a4bSAnika Henke $this->doc .= '</div>' . DOKU_LF; 5790cecf9d5Sandi } 5800cecf9d5Sandi 5813dd5c225SAndreas Gohr /** 5823dd5c225SAndreas Gohr * Output unformatted $text 5833dd5c225SAndreas Gohr * 5843dd5c225SAndreas Gohr * Defaults to $this->cdata() 5853dd5c225SAndreas Gohr * 5863dd5c225SAndreas Gohr * @param string $text 5873dd5c225SAndreas Gohr */ 588faf3f01bSAndreas Gohr public function unformatted($text) 589faf3f01bSAndreas Gohr { 590a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 5910cecf9d5Sandi } 5920cecf9d5Sandi 5930cecf9d5Sandi /** 5943dd5c225SAndreas Gohr * Start a block quote 5953dd5c225SAndreas Gohr */ 596faf3f01bSAndreas Gohr public function quote_open() 597faf3f01bSAndreas Gohr { 59896331712SAnika Henke $this->doc .= '<blockquote><div class="no">' . DOKU_LF; 5990cecf9d5Sandi } 6000cecf9d5Sandi 6013dd5c225SAndreas Gohr /** 6023dd5c225SAndreas Gohr * Stop a block quote 6033dd5c225SAndreas Gohr */ 604faf3f01bSAndreas Gohr public function quote_close() 605faf3f01bSAndreas Gohr { 60696331712SAnika Henke $this->doc .= '</div></blockquote>' . DOKU_LF; 6070cecf9d5Sandi } 6080cecf9d5Sandi 6093dd5c225SAndreas Gohr /** 6103dd5c225SAndreas Gohr * Output preformatted text 6113dd5c225SAndreas Gohr * 6123dd5c225SAndreas Gohr * @param string $text 6133dd5c225SAndreas Gohr */ 614faf3f01bSAndreas Gohr public function preformatted($text) 615faf3f01bSAndreas Gohr { 616c9250713SAnika Henke $this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text), "\n\r") . '</pre>' . DOKU_LF; 6173d491f75SAndreas Gohr } 6183d491f75SAndreas Gohr 6193dd5c225SAndreas Gohr /** 6203dd5c225SAndreas Gohr * Display text as file content, optionally syntax highlighted 6213dd5c225SAndreas Gohr * 6223dd5c225SAndreas Gohr * @param string $text text to show 6233dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6243dd5c225SAndreas Gohr * @param string $filename file path label 625e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6263dd5c225SAndreas Gohr */ 627faf3f01bSAndreas Gohr public function file($text, $language = null, $filename = null, $options = null) 628faf3f01bSAndreas Gohr { 629e2d88156SLarsDW223 $this->_highlight('file', $text, $language, $filename, $options); 6303d491f75SAndreas Gohr } 6313d491f75SAndreas Gohr 6323dd5c225SAndreas Gohr /** 6333dd5c225SAndreas Gohr * Display text as code content, optionally syntax highlighted 6343dd5c225SAndreas Gohr * 6353dd5c225SAndreas Gohr * @param string $text text to show 6363dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6373dd5c225SAndreas Gohr * @param string $filename file path label 638e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6393dd5c225SAndreas Gohr */ 640faf3f01bSAndreas Gohr public function code($text, $language = null, $filename = null, $options = null) 641faf3f01bSAndreas Gohr { 642e2d88156SLarsDW223 $this->_highlight('code', $text, $language, $filename, $options); 6433d491f75SAndreas Gohr } 6443d491f75SAndreas Gohr 6450cecf9d5Sandi /** 6463d491f75SAndreas Gohr * Use GeSHi to highlight language syntax in code and file blocks 6473fd0b676Sandi * 6483dd5c225SAndreas Gohr * @param string $type code|file 6493dd5c225SAndreas Gohr * @param string $text text to show 6503dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6513dd5c225SAndreas Gohr * @param string $filename file path label 652e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 653faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 6540cecf9d5Sandi */ 655faf3f01bSAndreas Gohr public function _highlight($type, $text, $language = null, $filename = null, $options = null) 656faf3f01bSAndreas Gohr { 6573d491f75SAndreas Gohr global $ID; 6583d491f75SAndreas Gohr global $lang; 659ec57f119SLarsDW223 global $INPUT; 6603d491f75SAndreas Gohr 661bf8f8509SAndreas Gohr $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language ?? ''); 66256bd9509SPhy 6633d491f75SAndreas Gohr if ($filename) { 664190c56e8SAndreas Gohr // add icon 665faf3f01bSAndreas Gohr [$ext] = mimetype($filename, false); 666190c56e8SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 667190c56e8SAndreas Gohr $class = 'mediafile mf_' . $class; 668190c56e8SAndreas Gohr 669ec57f119SLarsDW223 $offset = 0; 670ec57f119SLarsDW223 if ($INPUT->has('codeblockOffset')) { 671ec57f119SLarsDW223 $offset = $INPUT->str('codeblockOffset'); 672ec57f119SLarsDW223 } 6733d491f75SAndreas Gohr $this->doc .= '<dl class="' . $type . '">' . DOKU_LF; 67464159a61SAndreas Gohr $this->doc .= '<dt><a href="' . 67564159a61SAndreas Gohr exportlink( 67664159a61SAndreas Gohr $ID, 67764159a61SAndreas Gohr 'code', 678faf3f01bSAndreas Gohr ['codeblock' => $offset + $this->_codeblock] 67964159a61SAndreas Gohr ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; 6803d491f75SAndreas Gohr $this->doc .= hsc($filename); 6813d491f75SAndreas Gohr $this->doc .= '</a></dt>' . DOKU_LF . '<dd>'; 6823d491f75SAndreas Gohr } 6830cecf9d5Sandi 6846c16a3a9Sfiwswe if (str_starts_with($text, "\n")) { 685d43aac1cSGina Haeussge $text = substr($text, 1); 686d43aac1cSGina Haeussge } 6876c16a3a9Sfiwswe if (str_ends_with($text, "\n")) { 688d43aac1cSGina Haeussge $text = substr($text, 0, -1); 689d43aac1cSGina Haeussge } 690d43aac1cSGina Haeussge 691a056e285SPhy if (empty($language)) { // empty is faster than is_null and can prevent '' string 6923d491f75SAndreas Gohr $this->doc .= '<pre class="' . $type . '">' . $this->_xmlEntities($text) . '</pre>' . DOKU_LF; 6930cecf9d5Sandi } else { 6943d491f75SAndreas Gohr $class = 'code'; //we always need the code class to make the syntax highlighting apply 6953d491f75SAndreas Gohr if ($type != 'code') $class .= ' ' . $type; 6963d491f75SAndreas Gohr 69764159a61SAndreas Gohr $this->doc .= "<pre class=\"$class $language\">" . 69864159a61SAndreas Gohr p_xhtml_cached_geshi($text, $language, '', $options) . 69964159a61SAndreas Gohr '</pre>' . DOKU_LF; 7000cecf9d5Sandi } 7013d491f75SAndreas Gohr 7023d491f75SAndreas Gohr if ($filename) { 7033d491f75SAndreas Gohr $this->doc .= '</dd></dl>' . DOKU_LF; 7043d491f75SAndreas Gohr } 7053d491f75SAndreas Gohr 7063d491f75SAndreas Gohr $this->_codeblock++; 7070cecf9d5Sandi } 7080cecf9d5Sandi 7093dd5c225SAndreas Gohr /** 7103dd5c225SAndreas Gohr * Format an acronym 7113dd5c225SAndreas Gohr * 7123dd5c225SAndreas Gohr * Uses $this->acronyms 7133dd5c225SAndreas Gohr * 7143dd5c225SAndreas Gohr * @param string $acronym 7153dd5c225SAndreas Gohr */ 716faf3f01bSAndreas Gohr public function acronym($acronym) 717faf3f01bSAndreas Gohr { 7180cecf9d5Sandi 7190cecf9d5Sandi if (array_key_exists($acronym, $this->acronyms)) { 720433bef32Sandi $title = $this->_xmlEntities($this->acronyms[$acronym]); 7210cecf9d5Sandi 722940db3a3SAnika Henke $this->doc .= '<abbr title="' . $title 723940db3a3SAnika Henke . '">' . $this->_xmlEntities($acronym) . '</abbr>'; 7240cecf9d5Sandi } else { 725a2d649c4Sandi $this->doc .= $this->_xmlEntities($acronym); 7260cecf9d5Sandi } 7270cecf9d5Sandi } 7280cecf9d5Sandi 7293dd5c225SAndreas Gohr /** 7303dd5c225SAndreas Gohr * Format a smiley 7313dd5c225SAndreas Gohr * 7323dd5c225SAndreas Gohr * Uses $this->smiley 7333dd5c225SAndreas Gohr * 7343dd5c225SAndreas Gohr * @param string $smiley 7353dd5c225SAndreas Gohr */ 736faf3f01bSAndreas Gohr public function smiley($smiley) 737faf3f01bSAndreas Gohr { 738b09504a9SAndreas Gohr if (isset($this->smileys[$smiley])) { 739e44b94a4SAndreas Gohr $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] . 740b09504a9SAndreas Gohr '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />'; 7410cecf9d5Sandi } else { 742a2d649c4Sandi $this->doc .= $this->_xmlEntities($smiley); 7430cecf9d5Sandi } 7440cecf9d5Sandi } 7450cecf9d5Sandi 7463dd5c225SAndreas Gohr /** 7473dd5c225SAndreas Gohr * Format an entity 7483dd5c225SAndreas Gohr * 7493dd5c225SAndreas Gohr * Entities are basically small text replacements 7503dd5c225SAndreas Gohr * 7513dd5c225SAndreas Gohr * Uses $this->entities 7523dd5c225SAndreas Gohr * 7533dd5c225SAndreas Gohr * @param string $entity 7544de671bcSandi */ 755faf3f01bSAndreas Gohr public function entity($entity) 756faf3f01bSAndreas Gohr { 7570cecf9d5Sandi if (array_key_exists($entity, $this->entities)) { 758a2d649c4Sandi $this->doc .= $this->entities[$entity]; 7590cecf9d5Sandi } else { 760a2d649c4Sandi $this->doc .= $this->_xmlEntities($entity); 7610cecf9d5Sandi } 7620cecf9d5Sandi } 7630cecf9d5Sandi 7643dd5c225SAndreas Gohr /** 7653dd5c225SAndreas Gohr * Typographically format a multiply sign 7663dd5c225SAndreas Gohr * 7673dd5c225SAndreas Gohr * Example: ($x=640, $y=480) should result in "640×480" 7683dd5c225SAndreas Gohr * 7693dd5c225SAndreas Gohr * @param string|int $x first value 7703dd5c225SAndreas Gohr * @param string|int $y second value 7713dd5c225SAndreas Gohr */ 772faf3f01bSAndreas Gohr public function multiplyentity($x, $y) 773faf3f01bSAndreas Gohr { 774a2d649c4Sandi $this->doc .= "$x×$y"; 7750cecf9d5Sandi } 7760cecf9d5Sandi 7773dd5c225SAndreas Gohr /** 7783dd5c225SAndreas Gohr * Render an opening single quote char (language specific) 7793dd5c225SAndreas Gohr */ 780faf3f01bSAndreas Gohr public function singlequoteopening() 781faf3f01bSAndreas Gohr { 78271b40da2SAnika Henke global $lang; 78371b40da2SAnika Henke $this->doc .= $lang['singlequoteopening']; 7840cecf9d5Sandi } 7850cecf9d5Sandi 7863dd5c225SAndreas Gohr /** 7873dd5c225SAndreas Gohr * Render a closing single quote char (language specific) 7883dd5c225SAndreas Gohr */ 789faf3f01bSAndreas Gohr public function singlequoteclosing() 790faf3f01bSAndreas Gohr { 79171b40da2SAnika Henke global $lang; 79271b40da2SAnika Henke $this->doc .= $lang['singlequoteclosing']; 7930cecf9d5Sandi } 7940cecf9d5Sandi 7953dd5c225SAndreas Gohr /** 7963dd5c225SAndreas Gohr * Render an apostrophe char (language specific) 7973dd5c225SAndreas Gohr */ 798faf3f01bSAndreas Gohr public function apostrophe() 799faf3f01bSAndreas Gohr { 80057d757d1SAndreas Gohr global $lang; 801a8bd192aSAndreas Gohr $this->doc .= $lang['apostrophe']; 80257d757d1SAndreas Gohr } 80357d757d1SAndreas Gohr 8043dd5c225SAndreas Gohr /** 8053dd5c225SAndreas Gohr * Render an opening double quote char (language specific) 8063dd5c225SAndreas Gohr */ 807faf3f01bSAndreas Gohr public function doublequoteopening() 808faf3f01bSAndreas Gohr { 80971b40da2SAnika Henke global $lang; 81071b40da2SAnika Henke $this->doc .= $lang['doublequoteopening']; 8110cecf9d5Sandi } 8120cecf9d5Sandi 8133dd5c225SAndreas Gohr /** 8143dd5c225SAndreas Gohr * Render an closinging double quote char (language specific) 8153dd5c225SAndreas Gohr */ 816faf3f01bSAndreas Gohr public function doublequoteclosing() 817faf3f01bSAndreas Gohr { 81871b40da2SAnika Henke global $lang; 81971b40da2SAnika Henke $this->doc .= $lang['doublequoteclosing']; 8200cecf9d5Sandi } 8210cecf9d5Sandi 8220cecf9d5Sandi /** 8233dd5c225SAndreas Gohr * Render a CamelCase link 8243dd5c225SAndreas Gohr * 8253dd5c225SAndreas Gohr * @param string $link The link name 826122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8270c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8280c4c0281SGerrit Uitslag * 8293dd5c225SAndreas Gohr * @see http://en.wikipedia.org/wiki/CamelCase 8300cecf9d5Sandi */ 831faf3f01bSAndreas Gohr public function camelcaselink($link, $returnonly = false) 832faf3f01bSAndreas Gohr { 833122f2d46SAndreas Böhler if ($returnonly) { 834122f2d46SAndreas Böhler return $this->internallink($link, $link, null, true); 835122f2d46SAndreas Böhler } else { 83611d0aa47Sandi $this->internallink($link, $link); 8370cecf9d5Sandi } 838122f2d46SAndreas Böhler } 8390cecf9d5Sandi 8403dd5c225SAndreas Gohr /** 8413dd5c225SAndreas Gohr * Render a page local link 8423dd5c225SAndreas Gohr * 8433dd5c225SAndreas Gohr * @param string $hash hash link identifier 8443dd5c225SAndreas Gohr * @param string $name name for the link 845122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8460c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8473dd5c225SAndreas Gohr */ 848faf3f01bSAndreas Gohr public function locallink($hash, $name = null, $returnonly = false) 849faf3f01bSAndreas Gohr { 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 * 872f23eef27SGerrit Uitslag * @param string $id pageid 873f23eef27SGerrit Uitslag * @param string|null $name link name 874f23eef27SGerrit Uitslag * @param string|null $search adds search url param 875f23eef27SGerrit Uitslag * @param bool $returnonly whether to return html or write to doc attribute 876f23eef27SGerrit Uitslag * @param string $linktype type to set use of headings 877f23eef27SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 878faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 879cffcc403Sandi */ 880faf3f01bSAndreas Gohr public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') 881faf3f01bSAndreas Gohr { 882ba11bd29Sandi global $conf; 88337e34a5eSandi global $ID; 884c4dda6afSAnika Henke global $INFO; 88544653a53SAdrian Lang 8863d5e07d9SAdrian Lang $params = ''; 8873d5e07d9SAdrian Lang $parts = explode('?', $id, 2); 8883d5e07d9SAdrian Lang if (count($parts) === 2) { 8893d5e07d9SAdrian Lang $id = $parts[0]; 8903d5e07d9SAdrian Lang $params = $parts[1]; 89144653a53SAdrian Lang } 89244653a53SAdrian Lang 893fda14ffcSIzidor Matušov // For empty $id we need to know the current $ID 894fda14ffcSIzidor Matušov // We need this check because _simpleTitle needs 895fda14ffcSIzidor Matušov // correct $id and resolve_pageid() use cleanID($id) 896fda14ffcSIzidor Matušov // (some things could be lost) 897fda14ffcSIzidor Matušov if ($id === '') { 898fda14ffcSIzidor Matušov $id = $ID; 899fda14ffcSIzidor Matušov } 900fda14ffcSIzidor Matušov 9010339c872Sjan // default name is based on $id as given 9020339c872Sjan $default = $this->_simpleTitle($id); 903ad32e47eSAndreas Gohr 9040339c872Sjan // now first resolve and clean up the $id 9058c6be208SAndreas Gohr $id = (new PageResolver($ID))->resolveId($id, $this->date_at, true); 9068c6be208SAndreas Gohr $exists = page_exists($id, $this->date_at, false, true); 907fda14ffcSIzidor Matušov 908faf3f01bSAndreas Gohr $link = []; 909fe9ec250SChris Smith $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 9100e1c636eSandi if (!$isImage) { 9110e1c636eSandi if ($exists) { 912ba11bd29Sandi $class = 'wikilink1'; 9130cecf9d5Sandi } else { 914ba11bd29Sandi $class = 'wikilink2'; 91544a6b4c7SAndreas Gohr $link['rel'] = 'nofollow'; 9160cecf9d5Sandi } 9170cecf9d5Sandi } else { 918ba11bd29Sandi $class = 'media'; 9190cecf9d5Sandi } 9200cecf9d5Sandi 921a1685bedSandi //keep hash anchor 922faf3f01bSAndreas Gohr [$id, $hash] = sexplode('#', $id, 2); 923943dedc6SAndreas Gohr if (!empty($hash)) $hash = $this->_headerToLink($hash); 924a1685bedSandi 925ba11bd29Sandi //prepare for formating 926ba11bd29Sandi $link['target'] = $conf['target']['wiki']; 927ba11bd29Sandi $link['style'] = ''; 928ba11bd29Sandi $link['pre'] = ''; 929ba11bd29Sandi $link['suf'] = ''; 930bbac1489SPhy $link['more'] = 'data-wiki-id="' . $id . '"'; // id is already cleaned 931ba11bd29Sandi $link['class'] = $class; 9325c2eed9aSlisps if ($this->date_at) { 933912a6d48SPhy $params = $params . '&at=' . rawurlencode($this->date_at); 9345c2eed9aSlisps } 93544653a53SAdrian Lang $link['url'] = wl($id, $params); 936ba11bd29Sandi $link['name'] = $name; 937ba11bd29Sandi $link['title'] = $id; 938723d78dbSandi //add search string 939723d78dbSandi if ($search) { 940546d3a99SAndreas Gohr ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 941546d3a99SAndreas Gohr if (is_array($search)) { 942546d3a99SAndreas Gohr $search = array_map('rawurlencode', $search); 943faf3f01bSAndreas Gohr $link['url'] .= 's[]=' . implode('&s[]=', $search); 944546d3a99SAndreas Gohr } else { 945546d3a99SAndreas Gohr $link['url'] .= 's=' . rawurlencode($search); 946546d3a99SAndreas Gohr } 947723d78dbSandi } 948723d78dbSandi 949a1685bedSandi //keep hash 950a1685bedSandi if ($hash) $link['url'] .= '#' . $hash; 951a1685bedSandi 952ba11bd29Sandi //output formatted 953cffcc403Sandi if ($returnonly) { 954cffcc403Sandi return $this->_formatLink($link); 955cffcc403Sandi } else { 956a2d649c4Sandi $this->doc .= $this->_formatLink($link); 9570cecf9d5Sandi } 958cffcc403Sandi } 9590cecf9d5Sandi 9603dd5c225SAndreas Gohr /** 9613dd5c225SAndreas Gohr * Render an external link 9623dd5c225SAndreas Gohr * 9633dd5c225SAndreas Gohr * @param string $url full URL with scheme 9643dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 965122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9660c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9673dd5c225SAndreas Gohr */ 968faf3f01bSAndreas Gohr public function externallink($url, $name = null, $returnonly = false) 969faf3f01bSAndreas Gohr { 970b625487dSandi global $conf; 9710cecf9d5Sandi 972433bef32Sandi $name = $this->_getLinkTitle($name, $url, $isImage); 9736f0c5dbfSandi 974b52b1596SAndreas Gohr // url might be an attack vector, only allow registered protocols 975b52b1596SAndreas Gohr if (is_null($this->schemes)) $this->schemes = getSchemes(); 976faf3f01bSAndreas Gohr [$scheme] = explode('://', $url); 977b52b1596SAndreas Gohr $scheme = strtolower($scheme); 978b52b1596SAndreas Gohr if (!in_array($scheme, $this->schemes)) $url = ''; 979b52b1596SAndreas Gohr 980b52b1596SAndreas Gohr // is there still an URL? 981b52b1596SAndreas Gohr if (!$url) { 98249cef4fdSAndreas Böhler if ($returnonly) { 98349cef4fdSAndreas Böhler return $name; 98449cef4fdSAndreas Böhler } else { 985b52b1596SAndreas Gohr $this->doc .= $name; 98649cef4fdSAndreas Böhler } 987b52b1596SAndreas Gohr return; 988b52b1596SAndreas Gohr } 989b52b1596SAndreas Gohr 990b52b1596SAndreas Gohr // set class 9910cecf9d5Sandi if (!$isImage) { 992b625487dSandi $class = 'urlextern'; 9930cecf9d5Sandi } else { 994b625487dSandi $class = 'media'; 9950cecf9d5Sandi } 9960cecf9d5Sandi 997b625487dSandi //prepare for formating 998faf3f01bSAndreas Gohr $link = []; 999b625487dSandi $link['target'] = $conf['target']['extern']; 1000b625487dSandi $link['style'] = ''; 1001b625487dSandi $link['pre'] = ''; 1002b625487dSandi $link['suf'] = ''; 10035e163278SAndreas Gohr $link['more'] = ''; 1004b625487dSandi $link['class'] = $class; 1005b625487dSandi $link['url'] = $url; 1006914045f3SAndreas Gohr $link['rel'] = ''; 1007e1c10e4dSchris 1008b625487dSandi $link['name'] = $name; 1009433bef32Sandi $link['title'] = $this->_xmlEntities($url); 10105ddd0bbbSStarArmy if ($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; 1011914045f3SAndreas Gohr if ($conf['target']['extern']) $link['rel'] .= ' noopener'; 10120cecf9d5Sandi 1013b625487dSandi //output formatted 1014122f2d46SAndreas Böhler if ($returnonly) { 1015122f2d46SAndreas Böhler return $this->_formatLink($link); 1016122f2d46SAndreas Böhler } else { 1017a2d649c4Sandi $this->doc .= $this->_formatLink($link); 10180cecf9d5Sandi } 1019122f2d46SAndreas Böhler } 10200cecf9d5Sandi 10210cecf9d5Sandi /** 10223dd5c225SAndreas Gohr * Render an interwiki link 10233dd5c225SAndreas Gohr * 10243dd5c225SAndreas Gohr * You may want to use $this->_resolveInterWiki() here 10253dd5c225SAndreas Gohr * 10263dd5c225SAndreas Gohr * @param string $match original link - probably not much use 10273dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 10283dd5c225SAndreas Gohr * @param string $wikiName indentifier (shortcut) for the remote wiki 10293dd5c225SAndreas Gohr * @param string $wikiUri the fragment parsed from the original link 1030122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10310c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10320cecf9d5Sandi */ 1033faf3f01bSAndreas Gohr public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) 1034faf3f01bSAndreas Gohr { 1035b625487dSandi global $conf; 10360cecf9d5Sandi 1037faf3f01bSAndreas Gohr $link = []; 103897a3e4e3Sandi $link['target'] = $conf['target']['interwiki']; 103997a3e4e3Sandi $link['pre'] = ''; 104097a3e4e3Sandi $link['suf'] = ''; 10415e163278SAndreas Gohr $link['more'] = ''; 1042433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 1043914045f3SAndreas Gohr $link['rel'] = ''; 10440cecf9d5Sandi 104597a3e4e3Sandi //get interwiki URL 10466496c33fSGerrit Uitslag $exists = null; 10476496c33fSGerrit Uitslag $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 10480cecf9d5Sandi 104997a3e4e3Sandi if (!$isImage) { 10509d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 10519d2ddea4SAndreas Gohr $link['class'] = "interwiki iw_$class"; 10521c2d1019SAndreas Gohr } else { 10531c2d1019SAndreas Gohr $link['class'] = 'media'; 105497a3e4e3Sandi } 10550cecf9d5Sandi 105697a3e4e3Sandi //do we stay at the same server? Use local target 1057faf3f01bSAndreas Gohr if (strpos($url, DOKU_URL) === 0 || strpos($url, DOKU_BASE) === 0) { 105897a3e4e3Sandi $link['target'] = $conf['target']['wiki']; 105997a3e4e3Sandi } 10606496c33fSGerrit Uitslag if ($exists !== null && !$isImage) { 10616496c33fSGerrit Uitslag if ($exists) { 10626496c33fSGerrit Uitslag $link['class'] .= ' wikilink1'; 10636496c33fSGerrit Uitslag } else { 10646496c33fSGerrit Uitslag $link['class'] .= ' wikilink2'; 1065914045f3SAndreas Gohr $link['rel'] .= ' nofollow'; 10666496c33fSGerrit Uitslag } 10676496c33fSGerrit Uitslag } 1068914045f3SAndreas Gohr if ($conf['target']['interwiki']) $link['rel'] .= ' noopener'; 10690cecf9d5Sandi 107097a3e4e3Sandi $link['url'] = $url; 1071f7711f2bSAndreas Gohr $link['title'] = $this->_xmlEntities($link['url']); 107297a3e4e3Sandi 107397a3e4e3Sandi // output formatted 1074122f2d46SAndreas Böhler if ($returnonly) { 1075abde5980SPhy if ($url == '') return $link['name']; 1076122f2d46SAndreas Böhler return $this->_formatLink($link); 1077faf3f01bSAndreas Gohr } elseif ($url == '') { 1078faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1079faf3f01bSAndreas Gohr } else $this->doc .= $this->_formatLink($link); 1080122f2d46SAndreas Böhler } 10810cecf9d5Sandi 10820cecf9d5Sandi /** 10833dd5c225SAndreas Gohr * Link to windows share 10843dd5c225SAndreas Gohr * 10853dd5c225SAndreas Gohr * @param string $url the link 10863dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1087122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10880c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10890cecf9d5Sandi */ 1090faf3f01bSAndreas Gohr public function windowssharelink($url, $name = null, $returnonly = false) 1091faf3f01bSAndreas Gohr { 10921d47afe1Sandi global $conf; 10933dd5c225SAndreas Gohr 10941d47afe1Sandi //simple setup 1095faf3f01bSAndreas Gohr $link = []; 10961d47afe1Sandi $link['target'] = $conf['target']['windows']; 10971d47afe1Sandi $link['pre'] = ''; 10981d47afe1Sandi $link['suf'] = ''; 10991d47afe1Sandi $link['style'] = ''; 11000cecf9d5Sandi 1101433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 11020cecf9d5Sandi if (!$isImage) { 11031d47afe1Sandi $link['class'] = 'windows'; 11040cecf9d5Sandi } else { 11051d47afe1Sandi $link['class'] = 'media'; 11060cecf9d5Sandi } 11070cecf9d5Sandi 1108433bef32Sandi $link['title'] = $this->_xmlEntities($url); 11091d47afe1Sandi $url = str_replace('\\', '/', $url); 11101d47afe1Sandi $url = 'file:///' . $url; 11111d47afe1Sandi $link['url'] = $url; 11120cecf9d5Sandi 11131d47afe1Sandi //output formatted 1114122f2d46SAndreas Böhler if ($returnonly) { 1115122f2d46SAndreas Böhler return $this->_formatLink($link); 1116122f2d46SAndreas Böhler } else { 1117a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11180cecf9d5Sandi } 1119122f2d46SAndreas Böhler } 11200cecf9d5Sandi 11213dd5c225SAndreas Gohr /** 11223dd5c225SAndreas Gohr * Render a linked E-Mail Address 11233dd5c225SAndreas Gohr * 11243dd5c225SAndreas Gohr * Honors $conf['mailguard'] setting 11253dd5c225SAndreas Gohr * 11263dd5c225SAndreas Gohr * @param string $address Email-Address 11273dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1128122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 11290c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 11303dd5c225SAndreas Gohr */ 1131faf3f01bSAndreas Gohr public function emaillink($address, $name = null, $returnonly = false) 1132faf3f01bSAndreas Gohr { 113371352defSandi global $conf; 113471352defSandi //simple setup 1135faf3f01bSAndreas Gohr $link = []; 113671352defSandi $link['target'] = ''; 113771352defSandi $link['pre'] = ''; 113871352defSandi $link['suf'] = ''; 113971352defSandi $link['style'] = ''; 114071352defSandi $link['more'] = ''; 11410cecf9d5Sandi 1142c078fc55SAndreas Gohr $name = $this->_getLinkTitle($name, '', $isImage); 11430cecf9d5Sandi if (!$isImage) { 1144be96545cSAnika Henke $link['class'] = 'mail'; 11450cecf9d5Sandi } else { 1146be96545cSAnika Henke $link['class'] = 'media'; 11470cecf9d5Sandi } 11480cecf9d5Sandi 114907738714SAndreas Gohr $address = $this->_xmlEntities($address); 115000a7b5adSEsther Brunner $address = obfuscate($address); 1151faf3f01bSAndreas Gohr 115200a7b5adSEsther Brunner $title = $address; 11538c128049SAndreas Gohr 115471352defSandi if (empty($name)) { 115500a7b5adSEsther Brunner $name = $address; 115671352defSandi } 11570cecf9d5Sandi 1158776b36ecSAndreas Gohr if ($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1159776b36ecSAndreas Gohr 1160776b36ecSAndreas Gohr $link['url'] = 'mailto:' . $address; 116171352defSandi $link['name'] = $name; 116271352defSandi $link['title'] = $title; 11630cecf9d5Sandi 116471352defSandi //output formatted 1165122f2d46SAndreas Böhler if ($returnonly) { 1166122f2d46SAndreas Böhler return $this->_formatLink($link); 1167122f2d46SAndreas Böhler } else { 1168a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11690cecf9d5Sandi } 1170122f2d46SAndreas Böhler } 11710cecf9d5Sandi 11723dd5c225SAndreas Gohr /** 11733dd5c225SAndreas Gohr * Render an internal media file 11743dd5c225SAndreas Gohr * 11753dd5c225SAndreas Gohr * @param string $src media ID 11763dd5c225SAndreas Gohr * @param string $title descriptive text 11773dd5c225SAndreas Gohr * @param string $align left|center|right 11783dd5c225SAndreas Gohr * @param int $width width of media in pixel 11793dd5c225SAndreas Gohr * @param int $height height of media in pixel 11803dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11813dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 11823dd5c225SAndreas Gohr * @param bool $return return HTML instead of adding to $doc 11830c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11843dd5c225SAndreas Gohr */ 118595078f23SAndreas Gohr public function internalmedia( 118695078f23SAndreas Gohr $src, 118795078f23SAndreas Gohr $title = null, 118895078f23SAndreas Gohr $align = null, 118995078f23SAndreas Gohr $width = null, 119095078f23SAndreas Gohr $height = null, 119195078f23SAndreas Gohr $cache = null, 119295078f23SAndreas Gohr $linking = null, 119395078f23SAndreas Gohr $return = false 119495078f23SAndreas Gohr ) { 119537e34a5eSandi global $ID; 11968f34cf3dSMichael Große if (strpos($src, '#') !== false) { 1197faf3f01bSAndreas Gohr [$src, $hash] = sexplode('#', $src, 2); 11988f34cf3dSMichael Große } 11998c6be208SAndreas Gohr $src = (new MediaResolver($ID))->resolveId($src, $this->date_at, true); 12008c6be208SAndreas Gohr $exists = media_exists($src); 12010cecf9d5Sandi 1202d98d4540SBen Coburn $noLink = false; 1203faf3f01bSAndreas Gohr $render = $linking != 'linkonly'; 1204b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 12053685f775Sandi 1206faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src, false); 12076c16a3a9Sfiwswe if (str_starts_with($mime, 'image') && $render) { 120864159a61SAndreas Gohr $link['url'] = ml( 120964159a61SAndreas Gohr $src, 1210faf3f01bSAndreas Gohr [ 121164159a61SAndreas Gohr 'id' => $ID, 121264159a61SAndreas Gohr 'cache' => $cache, 121364159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1214faf3f01bSAndreas Gohr ], 121564159a61SAndreas Gohr ($linking == 'direct') 121664159a61SAndreas Gohr ); 1217f50634f0SAnika Henke } elseif (($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12182a2a2ba2SAnika Henke // don't link movies 121944881bd0Shenning.noren $noLink = true; 122055efc227SAndreas Gohr } else { 12212ca14335SEsther Brunner // add file icons 12229d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 12239d2ddea4SAndreas Gohr $link['class'] .= ' mediafile mf_' . $class; 122464159a61SAndreas Gohr $link['url'] = ml( 122564159a61SAndreas Gohr $src, 1226faf3f01bSAndreas Gohr [ 122764159a61SAndreas Gohr 'id' => $ID, 122864159a61SAndreas Gohr 'cache' => $cache, 122964159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1230faf3f01bSAndreas Gohr ], 123164159a61SAndreas Gohr true 123264159a61SAndreas Gohr ); 123391328684SMichael Hamann if ($exists) $link['title'] .= ' (' . filesize_h(filesize(mediaFN($src))) . ')'; 123455efc227SAndreas Gohr } 12353685f775Sandi 12368f34cf3dSMichael Große if (!empty($hash)) $link['url'] .= '#' . $hash; 123791df343aSAndreas Gohr 12386fe20453SGina Haeussge //markup non existing files 12394a24b459SKate Arzamastseva if (!$exists) { 12406fe20453SGina Haeussge $link['class'] .= ' wikilink2'; 12414a24b459SKate Arzamastseva } 12426fe20453SGina Haeussge 12433685f775Sandi //output formatted 1244f50634f0SAnika Henke if ($return) { 1245faf3f01bSAndreas Gohr if ($linking == 'nolink' || $noLink) { 1246faf3f01bSAndreas Gohr return $link['name']; 1247f50634f0SAnika Henke } else { 1248faf3f01bSAndreas Gohr return $this->_formatLink($link); 1249faf3f01bSAndreas Gohr } 1250faf3f01bSAndreas Gohr } elseif ($linking == 'nolink' || $noLink) { 1251faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1252faf3f01bSAndreas Gohr } else { 1253faf3f01bSAndreas Gohr $this->doc .= $this->_formatLink($link); 12540cecf9d5Sandi } 1255f50634f0SAnika Henke } 12560cecf9d5Sandi 12573dd5c225SAndreas Gohr /** 12583dd5c225SAndreas Gohr * Render an external media file 12593dd5c225SAndreas Gohr * 12603dd5c225SAndreas Gohr * @param string $src full media URL 12613dd5c225SAndreas Gohr * @param string $title descriptive text 12623dd5c225SAndreas Gohr * @param string $align left|center|right 12633dd5c225SAndreas Gohr * @param int $width width of media in pixel 12643dd5c225SAndreas Gohr * @param int $height height of media in pixel 12653dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 12663dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 1267410ee62aSAnika Henke * @param bool $return return HTML instead of adding to $doc 12680c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 12693dd5c225SAndreas Gohr */ 127095078f23SAndreas Gohr public function externalmedia( 127195078f23SAndreas Gohr $src, 127295078f23SAndreas Gohr $title = null, 127395078f23SAndreas Gohr $align = null, 127495078f23SAndreas Gohr $width = null, 127595078f23SAndreas Gohr $height = null, 127695078f23SAndreas Gohr $cache = null, 127795078f23SAndreas Gohr $linking = null, 127895078f23SAndreas Gohr $return = false 127995078f23SAndreas Gohr ) { 12806efc45a2SDmitry Katsubo if (link_isinterwiki($src)) { 1281faf3f01bSAndreas Gohr [$shortcut, $reference] = sexplode('>', $src, 2, ''); 12826efc45a2SDmitry Katsubo $exists = null; 12836efc45a2SDmitry Katsubo $src = $this->_resolveInterWiki($shortcut, $reference, $exists); 1284abde5980SPhy if ($src == '' && empty($title)) { 1285abde5980SPhy // make sure at least something will be shown in this case 1286abde5980SPhy $title = $reference; 1287abde5980SPhy } 12886efc45a2SDmitry Katsubo } 1289faf3f01bSAndreas Gohr [$src, $hash] = sexplode('#', $src, 2); 1290d98d4540SBen Coburn $noLink = false; 1291abde5980SPhy if ($src == '') { 1292abde5980SPhy // only output plaintext without link if there is no src 1293abde5980SPhy $noLink = true; 1294abde5980SPhy } 1295faf3f01bSAndreas Gohr $render = $linking != 'linkonly'; 1296b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1297b739ff0fSPierre Spring 1298faf3f01bSAndreas Gohr $link['url'] = ml($src, ['cache' => $cache]); 12993685f775Sandi 1300faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src, false); 13016c16a3a9Sfiwswe if (str_starts_with($mime, 'image') && $render) { 13022ca14335SEsther Brunner // link only jpeg images 130344881bd0Shenning.noren // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1304f50634f0SAnika Henke } elseif (($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 13052a2a2ba2SAnika Henke // don't link movies 130644881bd0Shenning.noren $noLink = true; 13072ca14335SEsther Brunner } else { 13082ca14335SEsther Brunner // add file icons 130927bf7924STom N Harris $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 131027bf7924STom N Harris $link['class'] .= ' mediafile mf_' . $class; 13112ca14335SEsther Brunner } 13122ca14335SEsther Brunner 131391df343aSAndreas Gohr if ($hash) $link['url'] .= '#' . $hash; 131491df343aSAndreas Gohr 13153685f775Sandi //output formatted 1316410ee62aSAnika Henke if ($return) { 1317410ee62aSAnika Henke if ($linking == 'nolink' || $noLink) return $link['name']; 1318410ee62aSAnika Henke else return $this->_formatLink($link); 1319faf3f01bSAndreas Gohr } elseif ($linking == 'nolink' || $noLink) { 1320faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1321faf3f01bSAndreas Gohr } else $this->doc .= $this->_formatLink($link); 1322410ee62aSAnika Henke } 13230cecf9d5Sandi 13244826ab45Sandi /** 13253db95becSAndreas Gohr * Renders an RSS feed 1326b625487dSandi * 13270c4c0281SGerrit Uitslag * @param string $url URL of the feed 13280c4c0281SGerrit Uitslag * @param array $params Finetuning of the output 13290c4c0281SGerrit Uitslag * 1330b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 1331b625487dSandi */ 1332faf3f01bSAndreas Gohr public function rss($url, $params) 1333faf3f01bSAndreas Gohr { 1334b625487dSandi global $lang; 13353db95becSAndreas Gohr global $conf; 13363db95becSAndreas Gohr 13373db95becSAndreas Gohr $feed = new FeedParser(); 133800077af8SAndreas Gohr $feed->set_feed_url($url); 1339b625487dSandi 1340b625487dSandi //disable warning while fetching 13413dd5c225SAndreas Gohr if (!defined('DOKU_E_LEVEL')) { 13423dd5c225SAndreas Gohr $elvl = error_reporting(E_ERROR); 13433dd5c225SAndreas Gohr } 13443db95becSAndreas Gohr $rc = $feed->init(); 13453dd5c225SAndreas Gohr if (isset($elvl)) { 13463dd5c225SAndreas Gohr error_reporting($elvl); 13473dd5c225SAndreas Gohr } 1348b625487dSandi 134938c6f603SRobin H. Johnson if ($params['nosort']) $feed->enable_order_by_date(false); 135038c6f603SRobin H. Johnson 13513db95becSAndreas Gohr //decide on start and end 13523db95becSAndreas Gohr if ($params['reverse']) { 13533db95becSAndreas Gohr $mod = -1; 13543db95becSAndreas Gohr $start = $feed->get_item_quantity() - 1; 13553db95becSAndreas Gohr $end = $start - ($params['max']); 1356b2a412b0SAndreas Gohr $end = ($end < -1) ? -1 : $end; 13573db95becSAndreas Gohr } else { 13583db95becSAndreas Gohr $mod = 1; 13593db95becSAndreas Gohr $start = 0; 13603db95becSAndreas Gohr $end = $feed->get_item_quantity(); 1361d91ab76fSMatt Perry $end = ($end > $params['max']) ? $params['max'] : $end; 13623db95becSAndreas Gohr } 13633db95becSAndreas Gohr 1364a2d649c4Sandi $this->doc .= '<ul class="rss">'; 13653db95becSAndreas Gohr if ($rc) { 13663db95becSAndreas Gohr for ($x = $start; $x != $end; $x += $mod) { 13671bde1582SAndreas Gohr $item = $feed->get_item($x); 13683db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 136953df38b0SAndreas Gohr 1370d2ea3363SAndreas Gohr $lnkurl = $item->get_permalink(); 137153df38b0SAndreas Gohr $title = html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8'); 137253df38b0SAndreas Gohr 137353df38b0SAndreas Gohr // support feeds without links 1374d2ea3363SAndreas Gohr if ($lnkurl) { 137553df38b0SAndreas Gohr $this->externallink($item->get_permalink(), $title); 1376d2ea3363SAndreas Gohr } else { 137753df38b0SAndreas Gohr $this->doc .= ' ' . hsc($item->get_title()); 1378d2ea3363SAndreas Gohr } 13793db95becSAndreas Gohr if ($params['author']) { 13801bde1582SAndreas Gohr $author = $item->get_author(0); 1381faf3f01bSAndreas Gohr if ($author instanceof Author) { 13821bde1582SAndreas Gohr $name = $author->get_name(); 13831bde1582SAndreas Gohr if (!$name) $name = $author->get_email(); 1384163c2842SPhy if ($name) $this->doc .= ' ' . $lang['by'] . ' ' . hsc($name); 13851bde1582SAndreas Gohr } 13863db95becSAndreas Gohr } 13873db95becSAndreas Gohr if ($params['date']) { 13882e7e0c29SAndreas Gohr $this->doc .= ' (' . $item->get_local_date($conf['dformat']) . ')'; 13893db95becSAndreas Gohr } 13901bde1582SAndreas Gohr if ($params['details']) { 139153df38b0SAndreas Gohr $desc = $item->get_description(); 139253df38b0SAndreas Gohr $desc = strip_tags($desc); 139353df38b0SAndreas Gohr $desc = html_entity_decode($desc, ENT_QUOTES, 'UTF-8'); 13943db95becSAndreas Gohr $this->doc .= '<div class="detail">'; 139553df38b0SAndreas Gohr $this->doc .= hsc($desc); 13963db95becSAndreas Gohr $this->doc .= '</div>'; 13973db95becSAndreas Gohr } 13983db95becSAndreas Gohr 13993db95becSAndreas Gohr $this->doc .= '</div></li>'; 1400b625487dSandi } 1401b625487dSandi } else { 14023db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1403a2d649c4Sandi $this->doc .= '<em>' . $lang['rssfailed'] . '</em>'; 1404b625487dSandi $this->externallink($url); 140545e147ccSAndreas Gohr if ($conf['allowdebug']) { 140645e147ccSAndreas Gohr $this->doc .= '<!--' . hsc($feed->error) . '-->'; 140745e147ccSAndreas Gohr } 14083db95becSAndreas Gohr $this->doc .= '</div></li>'; 1409b625487dSandi } 1410a2d649c4Sandi $this->doc .= '</ul>'; 1411b625487dSandi } 1412b625487dSandi 14133dd5c225SAndreas Gohr /** 14143dd5c225SAndreas Gohr * Start a table 14153dd5c225SAndreas Gohr * 14163dd5c225SAndreas Gohr * @param int $maxcols maximum number of columns 14173dd5c225SAndreas Gohr * @param int $numrows NOT IMPLEMENTED 14183dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14197d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14203dd5c225SAndreas Gohr */ 1421faf3f01bSAndreas Gohr public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) 1422faf3f01bSAndreas Gohr { 1423b5742cedSPierre Spring // initialize the row counter used for classes 1424b5742cedSPierre Spring $this->_counter['row_counter'] = 0; 1425619736fdSAdrian Lang $class = 'table'; 14260c4c0281SGerrit Uitslag if ($classes !== null) { 1427faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 14280c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14290c4c0281SGerrit Uitslag } 1430619736fdSAdrian Lang if ($pos !== null) { 143106917fceSMichael Große $hid = $this->_headerToLink($class, true); 1432faf3f01bSAndreas Gohr $data = []; 1433ec57f119SLarsDW223 $data['target'] = 'table'; 1434ec57f119SLarsDW223 $data['name'] = ''; 1435ec57f119SLarsDW223 $data['hid'] = $hid; 1436ec57f119SLarsDW223 $class .= ' ' . $this->startSectionEdit($pos, $data); 1437619736fdSAdrian Lang } 1438619736fdSAdrian Lang $this->doc .= '<div class="' . $class . '"><table class="inline">' . 1439619736fdSAdrian Lang DOKU_LF; 14400cecf9d5Sandi } 14410cecf9d5Sandi 14423dd5c225SAndreas Gohr /** 14433dd5c225SAndreas Gohr * Close a table 14443dd5c225SAndreas Gohr * 14453dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14463dd5c225SAndreas Gohr */ 1447faf3f01bSAndreas Gohr public function table_close($pos = null) 1448faf3f01bSAndreas Gohr { 1449a8574918SAnika Henke $this->doc .= '</table></div>' . DOKU_LF; 1450619736fdSAdrian Lang if ($pos !== null) { 145190df9a4dSAdrian Lang $this->finishSectionEdit($pos); 14520cecf9d5Sandi } 1453619736fdSAdrian Lang } 14540cecf9d5Sandi 14553dd5c225SAndreas Gohr /** 14563dd5c225SAndreas Gohr * Open a table header 14573dd5c225SAndreas Gohr */ 1458faf3f01bSAndreas Gohr public function tablethead_open() 1459faf3f01bSAndreas Gohr { 1460f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB . '<thead>' . DOKU_LF; 1461f05a1cc5SGerrit Uitslag } 1462f05a1cc5SGerrit Uitslag 14633dd5c225SAndreas Gohr /** 14643dd5c225SAndreas Gohr * Close a table header 14653dd5c225SAndreas Gohr */ 1466faf3f01bSAndreas Gohr public function tablethead_close() 1467faf3f01bSAndreas Gohr { 1468f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB . '</thead>' . DOKU_LF; 1469f05a1cc5SGerrit Uitslag } 1470f05a1cc5SGerrit Uitslag 14713dd5c225SAndreas Gohr /** 14725a93f869SAnika Henke * Open a table body 14735a93f869SAnika Henke */ 1474faf3f01bSAndreas Gohr public function tabletbody_open() 1475faf3f01bSAndreas Gohr { 14765a93f869SAnika Henke $this->doc .= DOKU_TAB . '<tbody>' . DOKU_LF; 14775a93f869SAnika Henke } 14785a93f869SAnika Henke 14795a93f869SAnika Henke /** 14805a93f869SAnika Henke * Close a table body 14815a93f869SAnika Henke */ 1482faf3f01bSAndreas Gohr public function tabletbody_close() 1483faf3f01bSAndreas Gohr { 14845a93f869SAnika Henke $this->doc .= DOKU_TAB . '</tbody>' . DOKU_LF; 14855a93f869SAnika Henke } 14865a93f869SAnika Henke 14875a93f869SAnika Henke /** 1488d2a99739SAndreas Gohr * Open a table footer 1489d2a99739SAndreas Gohr */ 1490faf3f01bSAndreas Gohr public function tabletfoot_open() 1491faf3f01bSAndreas Gohr { 149244f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB . '<tfoot>' . DOKU_LF; 1493d2a99739SAndreas Gohr } 1494d2a99739SAndreas Gohr 1495d2a99739SAndreas Gohr /** 1496d2a99739SAndreas Gohr * Close a table footer 1497d2a99739SAndreas Gohr */ 1498faf3f01bSAndreas Gohr public function tabletfoot_close() 1499faf3f01bSAndreas Gohr { 150044f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB . '</tfoot>' . DOKU_LF; 1501d2a99739SAndreas Gohr } 1502d2a99739SAndreas Gohr 1503d2a99739SAndreas Gohr /** 15043dd5c225SAndreas Gohr * Open a table row 15050c4c0281SGerrit Uitslag * 15067d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15073dd5c225SAndreas Gohr */ 1508faf3f01bSAndreas Gohr public function tablerow_open($classes = null) 1509faf3f01bSAndreas Gohr { 1510b5742cedSPierre Spring // initialize the cell counter used for classes 1511b5742cedSPierre Spring $this->_counter['cell_counter'] = 0; 1512b5742cedSPierre Spring $class = 'row' . $this->_counter['row_counter']++; 15130c4c0281SGerrit Uitslag if ($classes !== null) { 1514faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15150c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15160c4c0281SGerrit Uitslag } 1517b5742cedSPierre Spring $this->doc .= DOKU_TAB . '<tr class="' . $class . '">' . DOKU_LF . DOKU_TAB . DOKU_TAB; 15180cecf9d5Sandi } 15190cecf9d5Sandi 15203dd5c225SAndreas Gohr /** 15213dd5c225SAndreas Gohr * Close a table row 15223dd5c225SAndreas Gohr */ 1523faf3f01bSAndreas Gohr public function tablerow_close() 1524faf3f01bSAndreas Gohr { 1525a2d649c4Sandi $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 15260cecf9d5Sandi } 15270cecf9d5Sandi 15283dd5c225SAndreas Gohr /** 15293dd5c225SAndreas Gohr * Open a table header cell 15303dd5c225SAndreas Gohr * 15313dd5c225SAndreas Gohr * @param int $colspan 15323dd5c225SAndreas Gohr * @param string $align left|center|right 15333dd5c225SAndreas Gohr * @param int $rowspan 15347d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15353dd5c225SAndreas Gohr */ 1536faf3f01bSAndreas Gohr public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) 1537faf3f01bSAndreas Gohr { 1538b5742cedSPierre Spring $class = 'class="col' . $this->_counter['cell_counter']++; 15390cecf9d5Sandi if (!is_null($align)) { 1540b5742cedSPierre Spring $class .= ' ' . $align . 'align'; 15410cecf9d5Sandi } 15420c4c0281SGerrit Uitslag if ($classes !== null) { 1543faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15440c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15450c4c0281SGerrit Uitslag } 1546b5742cedSPierre Spring $class .= '"'; 1547b5742cedSPierre Spring $this->doc .= '<th ' . $class; 15480cecf9d5Sandi if ($colspan > 1) { 1549a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1550a2d649c4Sandi $this->doc .= ' colspan="' . $colspan . '"'; 15510cecf9d5Sandi } 155225b97867Shakan.sandell if ($rowspan > 1) { 155325b97867Shakan.sandell $this->doc .= ' rowspan="' . $rowspan . '"'; 155425b97867Shakan.sandell } 1555a2d649c4Sandi $this->doc .= '>'; 15560cecf9d5Sandi } 15570cecf9d5Sandi 15583dd5c225SAndreas Gohr /** 15593dd5c225SAndreas Gohr * Close a table header cell 15603dd5c225SAndreas Gohr */ 1561faf3f01bSAndreas Gohr public function tableheader_close() 1562faf3f01bSAndreas Gohr { 1563a2d649c4Sandi $this->doc .= '</th>'; 15640cecf9d5Sandi } 15650cecf9d5Sandi 15663dd5c225SAndreas Gohr /** 15673dd5c225SAndreas Gohr * Open a table cell 15683dd5c225SAndreas Gohr * 15693dd5c225SAndreas Gohr * @param int $colspan 15703dd5c225SAndreas Gohr * @param string $align left|center|right 15713dd5c225SAndreas Gohr * @param int $rowspan 15727d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15733dd5c225SAndreas Gohr */ 1574faf3f01bSAndreas Gohr public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) 1575faf3f01bSAndreas Gohr { 1576b5742cedSPierre Spring $class = 'class="col' . $this->_counter['cell_counter']++; 15770cecf9d5Sandi if (!is_null($align)) { 1578b5742cedSPierre Spring $class .= ' ' . $align . 'align'; 15790cecf9d5Sandi } 15800c4c0281SGerrit Uitslag if ($classes !== null) { 1581faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15820c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15830c4c0281SGerrit Uitslag } 1584b5742cedSPierre Spring $class .= '"'; 1585b5742cedSPierre Spring $this->doc .= '<td ' . $class; 15860cecf9d5Sandi if ($colspan > 1) { 1587a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1588a2d649c4Sandi $this->doc .= ' colspan="' . $colspan . '"'; 15890cecf9d5Sandi } 159025b97867Shakan.sandell if ($rowspan > 1) { 159125b97867Shakan.sandell $this->doc .= ' rowspan="' . $rowspan . '"'; 159225b97867Shakan.sandell } 1593a2d649c4Sandi $this->doc .= '>'; 15940cecf9d5Sandi } 15950cecf9d5Sandi 15963dd5c225SAndreas Gohr /** 15973dd5c225SAndreas Gohr * Close a table cell 15983dd5c225SAndreas Gohr */ 1599faf3f01bSAndreas Gohr public function tablecell_close() 1600faf3f01bSAndreas Gohr { 1601a2d649c4Sandi $this->doc .= '</td>'; 16020cecf9d5Sandi } 16030cecf9d5Sandi 1604cea664bdSLarsDW223 /** 1605cea664bdSLarsDW223 * Returns the current header level. 1606cea664bdSLarsDW223 * (required e.g. by the filelist plugin) 1607cea664bdSLarsDW223 * 1608cea664bdSLarsDW223 * @return int The current header level 1609cea664bdSLarsDW223 */ 1610faf3f01bSAndreas Gohr public function getLastlevel() 1611faf3f01bSAndreas Gohr { 1612cea664bdSLarsDW223 return $this->lastlevel; 1613cea664bdSLarsDW223 } 1614cea664bdSLarsDW223 16153dd5c225SAndreas Gohr #region Utility functions 16160cecf9d5Sandi 1617ba11bd29Sandi /** 16183fd0b676Sandi * Build a link 16193fd0b676Sandi * 16203fd0b676Sandi * Assembles all parts defined in $link returns HTML for the link 1621ba11bd29Sandi * 16220c4c0281SGerrit Uitslag * @param array $link attributes of a link 16230c4c0281SGerrit Uitslag * @return string 16240c4c0281SGerrit Uitslag * 1625ba11bd29Sandi * @author Andreas Gohr <andi@splitbrain.org> 1626ba11bd29Sandi */ 1627faf3f01bSAndreas Gohr public function _formatLink($link) 1628faf3f01bSAndreas Gohr { 1629ba11bd29Sandi //make sure the url is XHTML compliant (skip mailto) 16306c16a3a9Sfiwswe if (!str_starts_with($link['url'], 'mailto:')) { 1631ba11bd29Sandi $link['url'] = str_replace('&', '&', $link['url']); 1632ba11bd29Sandi $link['url'] = str_replace('&amp;', '&', $link['url']); 1633ba11bd29Sandi } 1634ba11bd29Sandi //remove double encodings in titles 1635ba11bd29Sandi $link['title'] = str_replace('&amp;', '&', $link['title']); 1636ba11bd29Sandi 1637453493f2SAndreas Gohr // be sure there are no bad chars in url or title 1638453493f2SAndreas Gohr // (we can't do this for name because it can contain an img tag) 1639faf3f01bSAndreas Gohr $link['url'] = strtr($link['url'], ['>' => '%3E', '<' => '%3C', '"' => '%22']); 1640faf3f01bSAndreas Gohr $link['title'] = strtr($link['title'], ['>' => '>', '<' => '<', '"' => '"']); 1641453493f2SAndreas Gohr 1642ba11bd29Sandi $ret = ''; 1643ba11bd29Sandi $ret .= $link['pre']; 1644ba11bd29Sandi $ret .= '<a href="' . $link['url'] . '"'; 1645bb4866bdSchris if (!empty($link['class'])) $ret .= ' class="' . $link['class'] . '"'; 1646bb4866bdSchris if (!empty($link['target'])) $ret .= ' target="' . $link['target'] . '"'; 1647bb4866bdSchris if (!empty($link['title'])) $ret .= ' title="' . $link['title'] . '"'; 1648bb4866bdSchris if (!empty($link['style'])) $ret .= ' style="' . $link['style'] . '"'; 1649914045f3SAndreas Gohr if (!empty($link['rel'])) $ret .= ' rel="' . trim($link['rel']) . '"'; 1650bb4866bdSchris if (!empty($link['more'])) $ret .= ' ' . $link['more']; 1651ba11bd29Sandi $ret .= '>'; 1652ba11bd29Sandi $ret .= $link['name']; 1653ba11bd29Sandi $ret .= '</a>'; 1654ba11bd29Sandi $ret .= $link['suf']; 1655ba11bd29Sandi return $ret; 1656ba11bd29Sandi } 1657ba11bd29Sandi 1658ba11bd29Sandi /** 16593fd0b676Sandi * Renders internal and external media 16603fd0b676Sandi * 16613dd5c225SAndreas Gohr * @param string $src media ID 16623dd5c225SAndreas Gohr * @param string $title descriptive text 16633dd5c225SAndreas Gohr * @param string $align left|center|right 16643dd5c225SAndreas Gohr * @param int $width width of media in pixel 16653dd5c225SAndreas Gohr * @param int $height height of media in pixel 16663dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 16673dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 16683dd5c225SAndreas Gohr * @return string 1669faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 16703fd0b676Sandi */ 167195078f23SAndreas Gohr public function _media( 167295078f23SAndreas Gohr $src, 167395078f23SAndreas Gohr $title = null, 167495078f23SAndreas Gohr $align = null, 167595078f23SAndreas Gohr $width = null, 167695078f23SAndreas Gohr $height = null, 167795078f23SAndreas Gohr $cache = null, 167895078f23SAndreas Gohr $render = true 167995078f23SAndreas Gohr ) { 16803fd0b676Sandi 16813fd0b676Sandi $ret = ''; 16823fd0b676Sandi 1683faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src); 16846c16a3a9Sfiwswe if (str_starts_with($mime, 'image')) { 1685b739ff0fSPierre Spring // first get the $title 1686b739ff0fSPierre Spring if (!is_null($title)) { 1687b739ff0fSPierre Spring $title = $this->_xmlEntities($title); 1688b739ff0fSPierre Spring } elseif ($ext == 'jpg' || $ext == 'jpeg') { 1689b739ff0fSPierre Spring //try to use the caption from IPTC/EXIF 1690b739ff0fSPierre Spring require_once(DOKU_INC . 'inc/JpegMeta.php'); 169167f9913dSAndreas Gohr $jpeg = new JpegMeta(mediaFN($src)); 1692faf3f01bSAndreas Gohr $cap = $jpeg->getTitle(); 16933dd5c225SAndreas Gohr if (!empty($cap)) { 1694b739ff0fSPierre Spring $title = $this->_xmlEntities($cap); 1695b739ff0fSPierre Spring } 1696b739ff0fSPierre Spring } 1697b739ff0fSPierre Spring if (!$render) { 1698b739ff0fSPierre Spring // if the picture is not supposed to be rendered 1699b739ff0fSPierre Spring // return the title of the picture 1700768be5a3SPhy if ($title === null || $title === "") { 1701b739ff0fSPierre Spring // just show the sourcename 1702faf3f01bSAndreas Gohr $title = $this->_xmlEntities(PhpString::basename(noNS($src))); 1703b739ff0fSPierre Spring } 1704b739ff0fSPierre Spring return $title; 1705b739ff0fSPierre Spring } 17063fd0b676Sandi //add image tag 170764159a61SAndreas Gohr $ret .= '<img src="' . ml( 170864159a61SAndreas Gohr $src, 1709faf3f01bSAndreas Gohr [ 1710faf3f01bSAndreas Gohr 'w' => $width, 1711faf3f01bSAndreas Gohr 'h' => $height, 171264159a61SAndreas Gohr 'cache' => $cache, 171364159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1714faf3f01bSAndreas Gohr ] 171564159a61SAndreas Gohr ) . '"'; 17163fd0b676Sandi $ret .= ' class="media' . $align . '"'; 17174732b197SAndreas Gohr $ret .= ' loading="lazy"'; 17183fd0b676Sandi 1719b739ff0fSPierre Spring if ($title) { 1720b739ff0fSPierre Spring $ret .= ' title="' . $title . '"'; 1721b739ff0fSPierre Spring $ret .= ' alt="' . $title . '"'; 17223fd0b676Sandi } else { 17233fd0b676Sandi $ret .= ' alt=""'; 17243fd0b676Sandi } 17253fd0b676Sandi 1726749bc7f1SAndreas Gohr if (!is_null($width)) { 17273fd0b676Sandi $ret .= ' width="' . $this->_xmlEntities($width) . '"'; 1728749bc7f1SAndreas Gohr } 17293fd0b676Sandi 1730749bc7f1SAndreas Gohr if (!is_null($height)) { 17313fd0b676Sandi $ret .= ' height="' . $this->_xmlEntities($height) . '"'; 1732749bc7f1SAndreas Gohr } 17333fd0b676Sandi 17343fd0b676Sandi $ret .= ' />'; 173517954bb5SAnika Henke } elseif (media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 17362a2a2ba2SAnika Henke // first get the $title 1737749bc7f1SAndreas Gohr $title ??= false; 17382a2a2ba2SAnika Henke if (!$render) { 173917954bb5SAnika Henke // if the file is not supposed to be rendered 174017954bb5SAnika Henke // return the title of the file (just the sourcename if there is no title) 1741faf3f01bSAndreas Gohr return $this->_xmlEntities($title ?: PhpString::basename(noNS($src))); 17422a2a2ba2SAnika Henke } 17432a2a2ba2SAnika Henke 1744faf3f01bSAndreas Gohr $att = []; 17452a2a2ba2SAnika Henke $att['class'] = "media$align"; 174617954bb5SAnika Henke if ($title) { 174717954bb5SAnika Henke $att['title'] = $title; 174817954bb5SAnika Henke } 17492a2a2ba2SAnika Henke 175017954bb5SAnika Henke if (media_supportedav($mime, 'video')) { 175117954bb5SAnika Henke //add video 175279e53fe5SAnika Henke $ret .= $this->_video($src, $width, $height, $att); 1753b44a5dceSAnika Henke } 175417954bb5SAnika Henke if (media_supportedav($mime, 'audio')) { 1755b44a5dceSAnika Henke //add audio 1756b44a5dceSAnika Henke $ret .= $this->_audio($src, $att); 175717954bb5SAnika Henke } 17583fd0b676Sandi } elseif ($mime == 'application/x-shockwave-flash') { 17591c882ba8SAndreas Gohr if (!$render) { 17601c882ba8SAndreas Gohr // if the flash is not supposed to be rendered 17611c882ba8SAndreas Gohr // return the title of the flash 17621c882ba8SAndreas Gohr if (!$title) { 17631c882ba8SAndreas Gohr // just show the sourcename 1764faf3f01bSAndreas Gohr $title = PhpString::basename(noNS($src)); 17651c882ba8SAndreas Gohr } 176607bf32b2SAndreas Gohr return $this->_xmlEntities($title); 17671c882ba8SAndreas Gohr } 17681c882ba8SAndreas Gohr 1769faf3f01bSAndreas Gohr $att = []; 177007bf32b2SAndreas Gohr $att['class'] = "media$align"; 177107bf32b2SAndreas Gohr if ($align == 'right') $att['align'] = 'right'; 177207bf32b2SAndreas Gohr if ($align == 'left') $att['align'] = 'left'; 17733dd5c225SAndreas Gohr $ret .= html_flashobject( 177495078f23SAndreas Gohr ml($src, ['cache' => $cache], true, '&'), 177595078f23SAndreas Gohr $width, 177695078f23SAndreas Gohr $height, 1777faf3f01bSAndreas Gohr ['quality' => 'high'], 177807bf32b2SAndreas Gohr null, 177907bf32b2SAndreas Gohr $att, 17803dd5c225SAndreas Gohr $this->_xmlEntities($title) 17813dd5c225SAndreas Gohr ); 17820f428d7dSAndreas Gohr } elseif ($title) { 17833fd0b676Sandi // well at least we have a title to display 17843fd0b676Sandi $ret .= $this->_xmlEntities($title); 17853fd0b676Sandi } else { 17865291ca3aSAndreas Gohr // just show the sourcename 1787faf3f01bSAndreas Gohr $ret .= $this->_xmlEntities(PhpString::basename(noNS($src))); 17883fd0b676Sandi } 17893fd0b676Sandi 17903fd0b676Sandi return $ret; 17913fd0b676Sandi } 17923fd0b676Sandi 17933dd5c225SAndreas Gohr /** 17943dd5c225SAndreas Gohr * Escape string for output 17953dd5c225SAndreas Gohr * 17963dd5c225SAndreas Gohr * @param $string 17973dd5c225SAndreas Gohr * @return string 17983dd5c225SAndreas Gohr */ 1799faf3f01bSAndreas Gohr public function _xmlEntities($string) 1800faf3f01bSAndreas Gohr { 1801f7711f2bSAndreas Gohr return hsc($string); 18020cecf9d5Sandi } 18030cecf9d5Sandi 1804de369923SAndreas Gohr 1805af587fa8Sandi /** 18063fd0b676Sandi * Construct a title and handle images in titles 18073fd0b676Sandi * 18083dd5c225SAndreas Gohr * @param string|array $title either string title or media array 18093dd5c225SAndreas Gohr * @param string $default default title if nothing else is found 18103dd5c225SAndreas Gohr * @param bool $isImage will be set to true if it's a media file 18113dd5c225SAndreas Gohr * @param null|string $id linked page id (used to extract title from first heading) 18123dd5c225SAndreas Gohr * @param string $linktype content|navigation 18133dd5c225SAndreas Gohr * @return string HTML of the title, might be full image tag or just escaped text 1814faf3f01bSAndreas Gohr * @author Harry Fuecks <hfuecks@gmail.com> 18153fd0b676Sandi */ 1816faf3f01bSAndreas Gohr public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') 1817faf3f01bSAndreas Gohr { 181844881bd0Shenning.noren $isImage = false; 181929657f9eSAndreas Gohr if (is_array($title)) { 182029657f9eSAndreas Gohr $isImage = true; 182129657f9eSAndreas Gohr return $this->_imageTitle($title); 182229657f9eSAndreas Gohr } elseif (is_null($title) || trim($title) == '') { 1823fe9ec250SChris Smith if (useHeading($linktype) && $id) { 182467c15eceSMichael Hamann $heading = p_get_first_heading($id); 1825f515db7fSAndreas Gohr if (!blank($heading)) { 1826433bef32Sandi return $this->_xmlEntities($heading); 1827bb0a59d4Sjan } 1828bb0a59d4Sjan } 1829433bef32Sandi return $this->_xmlEntities($default); 183068c26e6dSMichael Klier } else { 183168c26e6dSMichael Klier return $this->_xmlEntities($title); 18320cecf9d5Sandi } 18330cecf9d5Sandi } 18340cecf9d5Sandi 18350cecf9d5Sandi /** 18363dd5c225SAndreas Gohr * Returns HTML code for images used in link titles 18373fd0b676Sandi * 1838e0c26282SGerrit Uitslag * @param array $img 18393dd5c225SAndreas Gohr * @return string HTML img tag or similar 1840faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 18410cecf9d5Sandi */ 1842faf3f01bSAndreas Gohr public function _imageTitle($img) 1843faf3f01bSAndreas Gohr { 1844d9baf1a7SKazutaka Miyasaka global $ID; 1845d9baf1a7SKazutaka Miyasaka 1846d9baf1a7SKazutaka Miyasaka // some fixes on $img['src'] 1847d9baf1a7SKazutaka Miyasaka // see internalmedia() and externalmedia() 1848faf3f01bSAndreas Gohr [$img['src']] = explode('#', $img['src'], 2); 1849d9baf1a7SKazutaka Miyasaka if ($img['type'] == 'internalmedia') { 18508c6be208SAndreas Gohr $img['src'] = (new MediaResolver($ID))->resolveId($img['src'], $this->date_at, true); 1851d9baf1a7SKazutaka Miyasaka } 1852d9baf1a7SKazutaka Miyasaka 18533dd5c225SAndreas Gohr return $this->_media( 18543dd5c225SAndreas Gohr $img['src'], 18554826ab45Sandi $img['title'], 18564826ab45Sandi $img['align'], 18574826ab45Sandi $img['width'], 18584826ab45Sandi $img['height'], 18593dd5c225SAndreas Gohr $img['cache'] 18603dd5c225SAndreas Gohr ); 18610cecf9d5Sandi } 1862b739ff0fSPierre Spring 1863b739ff0fSPierre Spring /** 18643dd5c225SAndreas Gohr * helperfunction to return a basic link to a media 18653dd5c225SAndreas Gohr * 18663dd5c225SAndreas Gohr * used in internalmedia() and externalmedia() 1867b739ff0fSPierre Spring * 18683dd5c225SAndreas Gohr * @param string $src media ID 18693dd5c225SAndreas Gohr * @param string $title descriptive text 18703dd5c225SAndreas Gohr * @param string $align left|center|right 18713dd5c225SAndreas Gohr * @param int $width width of media in pixel 18723dd5c225SAndreas Gohr * @param int $height height of media in pixel 18733dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 18743dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 18753dd5c225SAndreas Gohr * @return array associative array with link config 1876faf3f01bSAndreas Gohr * @author Pierre Spring <pierre.spring@liip.ch> 1877b739ff0fSPierre Spring */ 1878faf3f01bSAndreas Gohr public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) 1879faf3f01bSAndreas Gohr { 1880b739ff0fSPierre Spring global $conf; 1881b739ff0fSPierre Spring 1882faf3f01bSAndreas Gohr $link = []; 1883b739ff0fSPierre Spring $link['class'] = 'media'; 1884b739ff0fSPierre Spring $link['style'] = ''; 1885b739ff0fSPierre Spring $link['pre'] = ''; 1886b739ff0fSPierre Spring $link['suf'] = ''; 1887b739ff0fSPierre Spring $link['more'] = ''; 1888b739ff0fSPierre Spring $link['target'] = $conf['target']['media']; 1889bc3d2252SAndreas Gohr if ($conf['target']['media']) $link['rel'] = 'noopener'; 1890b739ff0fSPierre Spring $link['title'] = $this->_xmlEntities($src); 1891b739ff0fSPierre Spring $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1892b739ff0fSPierre Spring 1893b739ff0fSPierre Spring return $link; 1894b739ff0fSPierre Spring } 189591459163SAnika Henke 18962a2a2ba2SAnika Henke /** 18972a2a2ba2SAnika Henke * Embed video(s) in HTML 18982a2a2ba2SAnika Henke * 18992a2a2ba2SAnika Henke * @param string $src - ID of video to embed 19002a2a2ba2SAnika Henke * @param int $width - width of the video in pixels 19012a2a2ba2SAnika Henke * @param int $height - height of the video in pixels 19022a2a2ba2SAnika Henke * @param array $atts - additional attributes for the <video> tag 1903f50634f0SAnika Henke * @return string 1904faf3f01bSAndreas Gohr * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 1905faf3f01bSAndreas Gohr * 1906faf3f01bSAndreas Gohr * @author Anika Henke <anika@selfthinker.org> 19072a2a2ba2SAnika Henke */ 1908faf3f01bSAndreas Gohr public function _video($src, $width, $height, $atts = null) 1909faf3f01bSAndreas Gohr { 19102a2a2ba2SAnika Henke // prepare width and height 1911faf3f01bSAndreas Gohr if (is_null($atts)) $atts = []; 19122a2a2ba2SAnika Henke $atts['width'] = (int)$width; 19132a2a2ba2SAnika Henke $atts['height'] = (int)$height; 19142a2a2ba2SAnika Henke if (!$atts['width']) $atts['width'] = 320; 19152a2a2ba2SAnika Henke if (!$atts['height']) $atts['height'] = 240; 19162a2a2ba2SAnika Henke 1917410ee62aSAnika Henke $posterUrl = ''; 1918faf3f01bSAndreas Gohr $files = []; 1919faf3f01bSAndreas Gohr $tracks = []; 1920410ee62aSAnika Henke $isExternal = media_isexternal($src); 1921410ee62aSAnika Henke 1922410ee62aSAnika Henke if ($isExternal) { 1923410ee62aSAnika Henke // take direct source for external files 1924a19c9aa0SGerrit Uitslag [/* ext */, $srcMime] = mimetype($src); 1925410ee62aSAnika Henke $files[$srcMime] = $src; 1926410ee62aSAnika Henke } else { 19273d7a9e0aSAnika Henke // prepare alternative formats 1928faf3f01bSAndreas Gohr $extensions = ['webm', 'ogv', 'mp4']; 1929410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 1930faf3f01bSAndreas Gohr $poster = media_alternativefiles($src, ['jpg', 'png']); 19310877a1f1SSchplurtz le Déboulonné $tracks = media_trackfiles($src); 193299f943f6SAnika Henke if (!empty($poster)) { 19332d338eabSAndreas Gohr $posterUrl = ml(reset($poster), '', true, '&'); 193499f943f6SAnika Henke } 1935410ee62aSAnika Henke } 19362a2a2ba2SAnika Henke 1937f50634f0SAnika Henke $out = ''; 193879e53fe5SAnika Henke // open video tag 1939f50634f0SAnika Henke $out .= '<video ' . buildAttributes($atts) . ' controls="controls"'; 19403641199aSAnika Henke if ($posterUrl) $out .= ' poster="' . hsc($posterUrl) . '"'; 1941f50634f0SAnika Henke $out .= '>' . NL; 19423641199aSAnika Henke $fallback = ''; 194379e53fe5SAnika Henke 194479e53fe5SAnika Henke // output source for each alternative video format 1945410ee62aSAnika Henke foreach ($files as $mime => $file) { 1946410ee62aSAnika Henke if ($isExternal) { 1947410ee62aSAnika Henke $url = $file; 1948410ee62aSAnika Henke $linkType = 'externalmedia'; 1949410ee62aSAnika Henke } else { 19502d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1951410ee62aSAnika Henke $linkType = 'internalmedia'; 1952410ee62aSAnika Henke } 1953faf3f01bSAndreas Gohr $title = empty($atts['title']) 1954faf3f01bSAndreas Gohr ? $this->_xmlEntities(PhpString::basename(noNS($file))) 1955faf3f01bSAndreas Gohr : $atts['title']; 19563d7a9e0aSAnika Henke 1957f50634f0SAnika Henke $out .= '<source src="' . hsc($url) . '" type="' . $mime . '" />' . NL; 195879e53fe5SAnika Henke // alternative content (just a link to the file) 195964159a61SAndreas Gohr $fallback .= $this->$linkType( 196064159a61SAndreas Gohr $file, 196164159a61SAndreas Gohr $title, 196264159a61SAndreas Gohr null, 196364159a61SAndreas Gohr null, 196464159a61SAndreas Gohr null, 196564159a61SAndreas Gohr $cache = null, 196664159a61SAndreas Gohr $linking = 'linkonly', 196764159a61SAndreas Gohr $return = true 196864159a61SAndreas Gohr ); 19693d7a9e0aSAnika Henke } 19702a2a2ba2SAnika Henke 19710877a1f1SSchplurtz le Déboulonné // output each track if any 19720877a1f1SSchplurtz le Déboulonné foreach ($tracks as $trackid => $info) { 1973faf3f01bSAndreas Gohr [$kind, $srclang] = array_map('hsc', $info); 197423c61bbeSSchplurtz le Déboulonné $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; 197523c61bbeSSchplurtz le Déboulonné $out .= "label=\"$srclang\" "; 19760877a1f1SSchplurtz le Déboulonné $out .= 'src="' . ml($trackid, '', true) . '">' . NL; 19770877a1f1SSchplurtz le Déboulonné } 19780877a1f1SSchplurtz le Déboulonné 19792a2a2ba2SAnika Henke // finish 19803641199aSAnika Henke $out .= $fallback; 1981f50634f0SAnika Henke $out .= '</video>' . NL; 1982f50634f0SAnika Henke return $out; 19832a2a2ba2SAnika Henke } 19842a2a2ba2SAnika Henke 1985b44a5dceSAnika Henke /** 1986b44a5dceSAnika Henke * Embed audio in HTML 1987b44a5dceSAnika Henke * 1988b44a5dceSAnika Henke * @param string $src - ID of audio to embed 19896d4af72aSAnika Henke * @param array $atts - additional attributes for the <audio> tag 1990f50634f0SAnika Henke * @return string 1991faf3f01bSAndreas Gohr * @author Anika Henke <anika@selfthinker.org> 1992faf3f01bSAndreas Gohr * 1993b44a5dceSAnika Henke */ 1994faf3f01bSAndreas Gohr public function _audio($src, $atts = []) 1995faf3f01bSAndreas Gohr { 1996faf3f01bSAndreas Gohr $files = []; 1997410ee62aSAnika Henke $isExternal = media_isexternal($src); 1998b44a5dceSAnika Henke 1999410ee62aSAnika Henke if ($isExternal) { 2000410ee62aSAnika Henke // take direct source for external files 2001a19c9aa0SGerrit Uitslag [/* ext */, $srcMime] = mimetype($src); 2002410ee62aSAnika Henke $files[$srcMime] = $src; 2003410ee62aSAnika Henke } else { 2004b44a5dceSAnika Henke // prepare alternative formats 2005faf3f01bSAndreas Gohr $extensions = ['ogg', 'mp3', 'wav']; 2006410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 2007410ee62aSAnika Henke } 2008b44a5dceSAnika Henke 2009f50634f0SAnika Henke $out = ''; 2010b44a5dceSAnika Henke // open audio tag 2011f50634f0SAnika Henke $out .= '<audio ' . buildAttributes($atts) . ' controls="controls">' . NL; 20123641199aSAnika Henke $fallback = ''; 2013b44a5dceSAnika Henke 2014b44a5dceSAnika Henke // output source for each alternative audio format 2015410ee62aSAnika Henke foreach ($files as $mime => $file) { 2016410ee62aSAnika Henke if ($isExternal) { 2017410ee62aSAnika Henke $url = $file; 2018410ee62aSAnika Henke $linkType = 'externalmedia'; 2019410ee62aSAnika Henke } else { 20202d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 2021410ee62aSAnika Henke $linkType = 'internalmedia'; 2022410ee62aSAnika Henke } 2023faf3f01bSAndreas Gohr $title = $atts['title'] ?: $this->_xmlEntities(PhpString::basename(noNS($file))); 2024b44a5dceSAnika Henke 2025f50634f0SAnika Henke $out .= '<source src="' . hsc($url) . '" type="' . $mime . '" />' . NL; 2026b44a5dceSAnika Henke // alternative content (just a link to the file) 202764159a61SAndreas Gohr $fallback .= $this->$linkType( 202864159a61SAndreas Gohr $file, 202964159a61SAndreas Gohr $title, 203064159a61SAndreas Gohr null, 203164159a61SAndreas Gohr null, 203264159a61SAndreas Gohr null, 203364159a61SAndreas Gohr $cache = null, 203464159a61SAndreas Gohr $linking = 'linkonly', 203564159a61SAndreas Gohr $return = true 203664159a61SAndreas Gohr ); 2037b44a5dceSAnika Henke } 2038b44a5dceSAnika Henke 2039b44a5dceSAnika Henke // finish 20403641199aSAnika Henke $out .= $fallback; 2041f50634f0SAnika Henke $out .= '</audio>' . NL; 2042f50634f0SAnika Henke return $out; 2043b44a5dceSAnika Henke } 2044b44a5dceSAnika Henke 20455c2eed9aSlisps /** 204652dc5eadSlisps * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 20475c2eed9aSlisps * which returns an existing media revision less or equal to rev or date_at 20485c2eed9aSlisps * 20495c2eed9aSlisps * @param string $media_id 20505c2eed9aSlisps * @access protected 20515c2eed9aSlisps * @return string revision ('' for current) 2052faf3f01bSAndreas Gohr * @author lisps 20535c2eed9aSlisps */ 2054faf3f01bSAndreas Gohr protected function _getLastMediaRevisionAt($media_id) 2055faf3f01bSAndreas Gohr { 205652dc5eadSlisps if (!$this->date_at || media_isexternal($media_id)) return ''; 2057252acce3SSatoshi Sahara $changelog = new MediaChangeLog($media_id); 2058252acce3SSatoshi Sahara return $changelog->getLastRevisionAt($this->date_at); 20595c2eed9aSlisps } 20605c2eed9aSlisps 20613dd5c225SAndreas Gohr #endregion 20620cecf9d5Sandi} 20630cecf9d5Sandi 2064e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 2065