10cecf9d5Sandi<?php 20c3a5702SAndreas Gohr 30c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 4db926724SAndreas Gohruse dokuwiki\Feed\FeedParser; 52cd6cc0aSAndreas Gohruse dokuwiki\File\MediaResolver; 62cd6cc0aSAndreas Gohruse dokuwiki\File\PageResolver; 7*73dc0a89SAndreas Gohruse dokuwiki\MailUtils; 8faf3f01bSAndreas Gohruse dokuwiki\Utf8\PhpString; 9faf3f01bSAndreas Gohruse SimplePie\Author; 100c3a5702SAndreas Gohr 11b625487dSandi/** 12b625487dSandi * Renderer for XHTML output 13b625487dSandi * 14b4f2363aSAndreas Gohr * This is DokuWiki's main renderer used to display page content in the wiki 15b4f2363aSAndreas Gohr * 16b625487dSandi * @author Harry Fuecks <hfuecks@gmail.com> 17b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 183dd5c225SAndreas Gohr * 190cecf9d5Sandi */ 20faf3f01bSAndreas Gohrclass Doku_Renderer_xhtml extends Doku_Renderer 21faf3f01bSAndreas Gohr{ 223dd5c225SAndreas Gohr /** @var array store the table of contents */ 23faf3f01bSAndreas Gohr public $toc = []; 240cecf9d5Sandi 253dd5c225SAndreas Gohr /** @var array A stack of section edit data */ 26faf3f01bSAndreas Gohr protected $sectionedits = []; 27de369923SAndreas Gohr 283dd5c225SAndreas Gohr /** @var int last section edit id, used by startSectionEdit */ 293dd5c225SAndreas Gohr protected $lastsecid = 0; 300cecf9d5Sandi 3116ec3e37SAndreas Gohr /** @var array a list of footnotes, list starts at 1! */ 32faf3f01bSAndreas Gohr protected $footnotes = []; 337764a90aSandi 343dd5c225SAndreas Gohr /** @var int current section level */ 353dd5c225SAndreas Gohr protected $lastlevel = 0; 363dd5c225SAndreas Gohr /** @var array section node tracker */ 37faf3f01bSAndreas Gohr protected $node = [0, 0, 0, 0, 0]; 383dd5c225SAndreas Gohr 393dd5c225SAndreas Gohr /** @var string temporary $doc store */ 403dd5c225SAndreas Gohr protected $store = ''; 413dd5c225SAndreas Gohr 423dd5c225SAndreas Gohr /** @var array global counter, for table classes etc. */ 43faf3f01bSAndreas Gohr protected $_counter = []; // 443dd5c225SAndreas Gohr 453dd5c225SAndreas Gohr /** @var int counts the code and file blocks, used to provide download links */ 463dd5c225SAndreas Gohr protected $_codeblock = 0; 473dd5c225SAndreas Gohr 483dd5c225SAndreas Gohr /** @var array list of allowed URL schemes */ 49faf3f01bSAndreas Gohr protected $schemes; 50b5742cedSPierre Spring 5190df9a4dSAdrian Lang /** 5290df9a4dSAdrian Lang * Register a new edit section range 5390df9a4dSAdrian Lang * 5442ea7f44SGerrit Uitslag * @param int $start The byte position for the edit start 55ec57f119SLarsDW223 * @param array $data Associative array with section data: 56ec57f119SLarsDW223 * Key 'name': the section name/title 57ec57f119SLarsDW223 * Key 'target': the target for the section edit, 58ec57f119SLarsDW223 * e.g. 'section' or 'table' 59ec57f119SLarsDW223 * Key 'hid': header id 60ec57f119SLarsDW223 * Key 'codeblockOffset': actual code block index 61ec57f119SLarsDW223 * Key 'start': set in startSectionEdit(), 62ec57f119SLarsDW223 * do not set yourself 63ec57f119SLarsDW223 * Key 'range': calculated from 'start' and 64ec57f119SLarsDW223 * $key in finishSectionEdit(), 65ec57f119SLarsDW223 * do not set yourself 6690df9a4dSAdrian Lang * @return string A marker class for the starting HTML element 6742ea7f44SGerrit Uitslag * 6890df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 6990df9a4dSAdrian Lang */ 70faf3f01bSAndreas Gohr public function startSectionEdit($start, $data) 71faf3f01bSAndreas Gohr { 72ec57f119SLarsDW223 if (!is_array($data)) { 73ac025fdfSAndreas Gohr msg( 74ac025fdfSAndreas Gohr sprintf( 75ac025fdfSAndreas Gohr 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', 76ac025fdfSAndreas Gohr hsc((string)$data) 7795078f23SAndreas Gohr ), 7895078f23SAndreas Gohr -1 79ac025fdfSAndreas Gohr ); 80ac025fdfSAndreas Gohr 81ac025fdfSAndreas Gohr // @deprecated 2018-04-14, backward compatibility 82ac025fdfSAndreas Gohr $args = func_get_args(); 83faf3f01bSAndreas Gohr $data = []; 84ac025fdfSAndreas Gohr if (isset($args[1])) $data['target'] = $args[1]; 85ac025fdfSAndreas Gohr if (isset($args[2])) $data['name'] = $args[2]; 86ac025fdfSAndreas Gohr if (isset($args[3])) $data['hid'] = $args[3]; 87ec57f119SLarsDW223 } 88ec57f119SLarsDW223 $data['secid'] = ++$this->lastsecid; 89ec57f119SLarsDW223 $data['start'] = $start; 90ec57f119SLarsDW223 $this->sectionedits[] = $data; 91ec57f119SLarsDW223 return 'sectionedit' . $data['secid']; 9290df9a4dSAdrian Lang } 9390df9a4dSAdrian Lang 9490df9a4dSAdrian Lang /** 9590df9a4dSAdrian Lang * Finish an edit section range 9690df9a4dSAdrian Lang * 9742ea7f44SGerrit Uitslag * @param int $end The byte position for the edit end; null for the rest of the page 9842ea7f44SGerrit Uitslag * 9990df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 10090df9a4dSAdrian Lang */ 101faf3f01bSAndreas Gohr public function finishSectionEdit($end = null, $hid = null) 102faf3f01bSAndreas Gohr { 103ad43fdbfSasivery if (count($this->sectionedits) == 0) { 1040d9f02ecSasivery return; 1050d9f02ecSasivery } 106ec57f119SLarsDW223 $data = array_pop($this->sectionedits); 107ec57f119SLarsDW223 if (!is_null($end) && $end <= $data['start']) { 10800c13053SAdrian Lang return; 10900c13053SAdrian Lang } 1102571786cSLarsDW223 if (!is_null($hid)) { 111ec57f119SLarsDW223 $data['hid'] .= $hid; 1122571786cSLarsDW223 } 113ec57f119SLarsDW223 $data['range'] = $data['start'] . '-' . (is_null($end) ? '' : $end); 114ec57f119SLarsDW223 unset($data['start']); 115faf3f01bSAndreas Gohr $this->doc .= '<!-- EDIT' . hsc(json_encode($data, JSON_THROW_ON_ERROR)) . ' -->'; 11690df9a4dSAdrian Lang } 11790df9a4dSAdrian Lang 1183dd5c225SAndreas Gohr /** 1193dd5c225SAndreas Gohr * Returns the format produced by this renderer. 1203dd5c225SAndreas Gohr * 1213dd5c225SAndreas Gohr * @return string always 'xhtml' 1223dd5c225SAndreas Gohr */ 123faf3f01bSAndreas Gohr public function getFormat() 124faf3f01bSAndreas Gohr { 1255f70445dSAndreas Gohr return 'xhtml'; 1265f70445dSAndreas Gohr } 1275f70445dSAndreas Gohr 1283dd5c225SAndreas Gohr /** 1293dd5c225SAndreas Gohr * Initialize the document 1303dd5c225SAndreas Gohr */ 131faf3f01bSAndreas Gohr public function document_start() 132faf3f01bSAndreas Gohr { 133c5a8fd96SAndreas Gohr //reset some internals 134faf3f01bSAndreas Gohr $this->toc = []; 1350cecf9d5Sandi } 1360cecf9d5Sandi 1373dd5c225SAndreas Gohr /** 1383dd5c225SAndreas Gohr * Finalize the document 1393dd5c225SAndreas Gohr */ 140faf3f01bSAndreas Gohr public function document_end() 141faf3f01bSAndreas Gohr { 14290df9a4dSAdrian Lang // Finish open section edits. 143faf3f01bSAndreas Gohr while ($this->sectionedits !== []) { 144ec57f119SLarsDW223 if ($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { 14590df9a4dSAdrian Lang // If there is only one section, do not write a section edit 14690df9a4dSAdrian Lang // marker. 14790df9a4dSAdrian Lang array_pop($this->sectionedits); 14890df9a4dSAdrian Lang } else { 149d9e36cbeSAdrian Lang $this->finishSectionEdit(); 15090df9a4dSAdrian Lang } 15190df9a4dSAdrian Lang } 15290df9a4dSAdrian Lang 153faf3f01bSAndreas Gohr if ($this->footnotes !== []) { 154a2d649c4Sandi $this->doc .= '<div class="footnotes">' . DOKU_LF; 155d74aace9Schris 15616ec3e37SAndreas Gohr foreach ($this->footnotes as $id => $footnote) { 157d74aace9Schris // check its not a placeholder that indicates actual footnote text is elsewhere 1586c16a3a9Sfiwswe if (!str_starts_with($footnote, "@@FNT")) { 159d74aace9Schris // open the footnote and set the anchor and backlink 160d74aace9Schris $this->doc .= '<div class="fn">'; 16116cc7ed7SAnika Henke $this->doc .= '<sup><a href="#fnt__' . $id . '" id="fn__' . $id . '" class="fn_bot">'; 16229bfcd16SAndreas Gohr $this->doc .= $id . ')</a></sup> ' . DOKU_LF; 163d74aace9Schris 164d74aace9Schris // get any other footnotes that use the same markup 165d74aace9Schris $alt = array_keys($this->footnotes, "@@FNT$id"); 166d74aace9Schris 167d74aace9Schris foreach ($alt as $ref) { 168d74aace9Schris // set anchor and backlink for the other footnotes 16916ec3e37SAndreas Gohr $this->doc .= ', <sup><a href="#fnt__' . ($ref) . '" id="fn__' . ($ref) . '" class="fn_bot">'; 17016ec3e37SAndreas Gohr $this->doc .= ($ref) . ')</a></sup> ' . DOKU_LF; 171d74aace9Schris } 172d74aace9Schris 173d74aace9Schris // add footnote markup and close this footnote 174694afa06SAnika Henke $this->doc .= '<div class="content">' . $footnote . '</div>'; 175d74aace9Schris $this->doc .= '</div>' . DOKU_LF; 176d74aace9Schris } 1770cecf9d5Sandi } 178a2d649c4Sandi $this->doc .= '</div>' . DOKU_LF; 1790cecf9d5Sandi } 180c5a8fd96SAndreas Gohr 181b8595a66SAndreas Gohr // Prepare the TOC 182851f2e89SAnika Henke global $conf; 18364159a61SAndreas Gohr if ( 18464159a61SAndreas Gohr $this->info['toc'] && 18564159a61SAndreas Gohr is_array($this->toc) && 18664159a61SAndreas Gohr $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] 18764159a61SAndreas Gohr ) { 188b8595a66SAndreas Gohr global $TOC; 189b8595a66SAndreas Gohr $TOC = $this->toc; 1900cecf9d5Sandi } 1913e55d035SAndreas Gohr 1923e55d035SAndreas Gohr // make sure there are no empty paragraphs 19327918226Schris $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 194e41c4da9SAndreas Gohr } 1950cecf9d5Sandi 1963dd5c225SAndreas Gohr /** 1973dd5c225SAndreas Gohr * Add an item to the TOC 1983dd5c225SAndreas Gohr * 1993dd5c225SAndreas Gohr * @param string $id the hash link 2003dd5c225SAndreas Gohr * @param string $text the text to display 2013dd5c225SAndreas Gohr * @param int $level the nesting level 2023dd5c225SAndreas Gohr */ 203faf3f01bSAndreas Gohr public function toc_additem($id, $text, $level) 204faf3f01bSAndreas Gohr { 205af587fa8Sandi global $conf; 206af587fa8Sandi 207c5a8fd96SAndreas Gohr //handle TOC 208c5a8fd96SAndreas Gohr if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 2097d91652aSAndreas Gohr $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 210c5a8fd96SAndreas Gohr } 211e7856beaSchris } 212e7856beaSchris 2133dd5c225SAndreas Gohr /** 2143dd5c225SAndreas Gohr * Render a heading 2153dd5c225SAndreas Gohr * 2163dd5c225SAndreas Gohr * @param string $text the text to display 2173dd5c225SAndreas Gohr * @param int $level header level 2183dd5c225SAndreas Gohr * @param int $pos byte position in the original source 219e3c00e6eSIain Hallam * @param bool $returnonly whether to return html or write to doc attribute 220e3c00e6eSIain Hallam * @return void|string writes to doc attribute or returns html depends on $returnonly 2213dd5c225SAndreas Gohr */ 222faf3f01bSAndreas Gohr public function header($text, $level, $pos, $returnonly = false) 223faf3f01bSAndreas Gohr { 22490df9a4dSAdrian Lang global $conf; 22590df9a4dSAdrian Lang 226f515db7fSAndreas Gohr if (blank($text)) return; //skip empty headlines 227e7856beaSchris 228e7856beaSchris $hid = $this->_headerToLink($text, true); 229e7856beaSchris 230e7856beaSchris //only add items within configured levels 231e7856beaSchris $this->toc_additem($hid, $text, $level); 232c5a8fd96SAndreas Gohr 23391459163SAnika Henke // adjust $node to reflect hierarchy of levels 23491459163SAnika Henke $this->node[$level - 1]++; 23591459163SAnika Henke if ($level < $this->lastlevel) { 23691459163SAnika Henke for ($i = 0; $i < $this->lastlevel - $level; $i++) { 23791459163SAnika Henke $this->node[$this->lastlevel - $i - 1] = 0; 23891459163SAnika Henke } 23991459163SAnika Henke } 24091459163SAnika Henke $this->lastlevel = $level; 24191459163SAnika Henke 24295078f23SAndreas Gohr if ( 24395078f23SAndreas Gohr $level <= $conf['maxseclevel'] && 244faf3f01bSAndreas Gohr $this->sectionedits !== [] && 245ec57f119SLarsDW223 $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' 2463dd5c225SAndreas Gohr ) { 2476c1f778cSAdrian Lang $this->finishSectionEdit($pos - 1); 24890df9a4dSAdrian Lang } 24990df9a4dSAdrian Lang 250e3c00e6eSIain Hallam // build the header 251e3c00e6eSIain Hallam $header = DOKU_LF . '<h' . $level; 25290df9a4dSAdrian Lang if ($level <= $conf['maxseclevel']) { 253faf3f01bSAndreas Gohr $data = []; 254ec57f119SLarsDW223 $data['target'] = 'section'; 255ec57f119SLarsDW223 $data['name'] = $text; 256ec57f119SLarsDW223 $data['hid'] = $hid; 257ec57f119SLarsDW223 $data['codeblockOffset'] = $this->_codeblock; 258e3c00e6eSIain Hallam $header .= ' class="' . $this->startSectionEdit($pos, $data) . '"'; 25990df9a4dSAdrian Lang } 260e3c00e6eSIain Hallam $header .= ' id="' . $hid . '">'; 261e3c00e6eSIain Hallam $header .= $this->_xmlEntities($text); 262e3c00e6eSIain Hallam $header .= "</h$level>" . DOKU_LF; 263e3c00e6eSIain Hallam 264e3c00e6eSIain Hallam if ($returnonly) { 265e3c00e6eSIain Hallam return $header; 266e3c00e6eSIain Hallam } else { 267e3c00e6eSIain Hallam $this->doc .= $header; 268e3c00e6eSIain Hallam } 2690cecf9d5Sandi } 2700cecf9d5Sandi 2713dd5c225SAndreas Gohr /** 2723dd5c225SAndreas Gohr * Open a new section 2733dd5c225SAndreas Gohr * 2743dd5c225SAndreas Gohr * @param int $level section level (as determined by the previous header) 2753dd5c225SAndreas Gohr */ 276faf3f01bSAndreas Gohr public function section_open($level) 277faf3f01bSAndreas Gohr { 2789864e7b1SAdrian Lang $this->doc .= '<div class="level' . $level . '">' . DOKU_LF; 2790cecf9d5Sandi } 2800cecf9d5Sandi 2813dd5c225SAndreas Gohr /** 2823dd5c225SAndreas Gohr * Close the current section 2833dd5c225SAndreas Gohr */ 284faf3f01bSAndreas Gohr public function section_close() 285faf3f01bSAndreas Gohr { 286a2d649c4Sandi $this->doc .= DOKU_LF . '</div>' . DOKU_LF; 2870cecf9d5Sandi } 2880cecf9d5Sandi 2893dd5c225SAndreas Gohr /** 2903dd5c225SAndreas Gohr * Render plain text data 2913dd5c225SAndreas Gohr * 2923dd5c225SAndreas Gohr * @param $text 2933dd5c225SAndreas Gohr */ 294faf3f01bSAndreas Gohr public function cdata($text) 295faf3f01bSAndreas Gohr { 296a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 2970cecf9d5Sandi } 2980cecf9d5Sandi 2993dd5c225SAndreas Gohr /** 3003dd5c225SAndreas Gohr * Open a paragraph 3013dd5c225SAndreas Gohr */ 302faf3f01bSAndreas Gohr public function p_open() 303faf3f01bSAndreas Gohr { 30459869a4bSAnika Henke $this->doc .= DOKU_LF . '<p>' . DOKU_LF; 3050cecf9d5Sandi } 3060cecf9d5Sandi 3073dd5c225SAndreas Gohr /** 3083dd5c225SAndreas Gohr * Close a paragraph 3093dd5c225SAndreas Gohr */ 310faf3f01bSAndreas Gohr public function p_close() 311faf3f01bSAndreas Gohr { 31259869a4bSAnika Henke $this->doc .= DOKU_LF . '</p>' . DOKU_LF; 3130cecf9d5Sandi } 3140cecf9d5Sandi 3153dd5c225SAndreas Gohr /** 3163dd5c225SAndreas Gohr * Create a line break 3173dd5c225SAndreas Gohr */ 318faf3f01bSAndreas Gohr public function linebreak() 319faf3f01bSAndreas Gohr { 320a2d649c4Sandi $this->doc .= '<br/>' . DOKU_LF; 3210cecf9d5Sandi } 3220cecf9d5Sandi 3233dd5c225SAndreas Gohr /** 3243dd5c225SAndreas Gohr * Create a horizontal line 3253dd5c225SAndreas Gohr */ 326faf3f01bSAndreas Gohr public function hr() 327faf3f01bSAndreas Gohr { 3284beabca9SAnika Henke $this->doc .= '<hr />' . DOKU_LF; 3290cecf9d5Sandi } 3300cecf9d5Sandi 3313dd5c225SAndreas Gohr /** 3323dd5c225SAndreas Gohr * Start strong (bold) formatting 3333dd5c225SAndreas Gohr */ 334faf3f01bSAndreas Gohr public function strong_open() 335faf3f01bSAndreas Gohr { 336a2d649c4Sandi $this->doc .= '<strong>'; 3370cecf9d5Sandi } 3380cecf9d5Sandi 3393dd5c225SAndreas Gohr /** 3403dd5c225SAndreas Gohr * Stop strong (bold) formatting 3413dd5c225SAndreas Gohr */ 342faf3f01bSAndreas Gohr public function strong_close() 343faf3f01bSAndreas Gohr { 344a2d649c4Sandi $this->doc .= '</strong>'; 3450cecf9d5Sandi } 3460cecf9d5Sandi 3473dd5c225SAndreas Gohr /** 3483dd5c225SAndreas Gohr * Start emphasis (italics) formatting 3493dd5c225SAndreas Gohr */ 350faf3f01bSAndreas Gohr public function emphasis_open() 351faf3f01bSAndreas Gohr { 352a2d649c4Sandi $this->doc .= '<em>'; 3530cecf9d5Sandi } 3540cecf9d5Sandi 3553dd5c225SAndreas Gohr /** 3563dd5c225SAndreas Gohr * Stop emphasis (italics) formatting 3573dd5c225SAndreas Gohr */ 358faf3f01bSAndreas Gohr public function emphasis_close() 359faf3f01bSAndreas Gohr { 360a2d649c4Sandi $this->doc .= '</em>'; 3610cecf9d5Sandi } 3620cecf9d5Sandi 3633dd5c225SAndreas Gohr /** 3643dd5c225SAndreas Gohr * Start underline formatting 3653dd5c225SAndreas Gohr */ 366faf3f01bSAndreas Gohr public function underline_open() 367faf3f01bSAndreas Gohr { 36802e51121SAnika Henke $this->doc .= '<em class="u">'; 3690cecf9d5Sandi } 3700cecf9d5Sandi 3713dd5c225SAndreas Gohr /** 3723dd5c225SAndreas Gohr * Stop underline formatting 3733dd5c225SAndreas Gohr */ 374faf3f01bSAndreas Gohr public function underline_close() 375faf3f01bSAndreas Gohr { 37602e51121SAnika Henke $this->doc .= '</em>'; 3770cecf9d5Sandi } 3780cecf9d5Sandi 3793dd5c225SAndreas Gohr /** 3803dd5c225SAndreas Gohr * Start monospace formatting 3813dd5c225SAndreas Gohr */ 382faf3f01bSAndreas Gohr public function monospace_open() 383faf3f01bSAndreas Gohr { 384a2d649c4Sandi $this->doc .= '<code>'; 3850cecf9d5Sandi } 3860cecf9d5Sandi 3873dd5c225SAndreas Gohr /** 3883dd5c225SAndreas Gohr * Stop monospace formatting 3893dd5c225SAndreas Gohr */ 390faf3f01bSAndreas Gohr public function monospace_close() 391faf3f01bSAndreas Gohr { 392a2d649c4Sandi $this->doc .= '</code>'; 3930cecf9d5Sandi } 3940cecf9d5Sandi 3953dd5c225SAndreas Gohr /** 3963dd5c225SAndreas Gohr * Start a subscript 3973dd5c225SAndreas Gohr */ 398faf3f01bSAndreas Gohr public function subscript_open() 399faf3f01bSAndreas Gohr { 400a2d649c4Sandi $this->doc .= '<sub>'; 4010cecf9d5Sandi } 4020cecf9d5Sandi 4033dd5c225SAndreas Gohr /** 4043dd5c225SAndreas Gohr * Stop a subscript 4053dd5c225SAndreas Gohr */ 406faf3f01bSAndreas Gohr public function subscript_close() 407faf3f01bSAndreas Gohr { 408a2d649c4Sandi $this->doc .= '</sub>'; 4090cecf9d5Sandi } 4100cecf9d5Sandi 4113dd5c225SAndreas Gohr /** 4123dd5c225SAndreas Gohr * Start a superscript 4133dd5c225SAndreas Gohr */ 414faf3f01bSAndreas Gohr public function superscript_open() 415faf3f01bSAndreas Gohr { 416a2d649c4Sandi $this->doc .= '<sup>'; 4170cecf9d5Sandi } 4180cecf9d5Sandi 4193dd5c225SAndreas Gohr /** 4203dd5c225SAndreas Gohr * Stop a superscript 4213dd5c225SAndreas Gohr */ 422faf3f01bSAndreas Gohr public function superscript_close() 423faf3f01bSAndreas Gohr { 424a2d649c4Sandi $this->doc .= '</sup>'; 4250cecf9d5Sandi } 4260cecf9d5Sandi 4273dd5c225SAndreas Gohr /** 4283dd5c225SAndreas Gohr * Start deleted (strike-through) formatting 4293dd5c225SAndreas Gohr */ 430faf3f01bSAndreas Gohr public function deleted_open() 431faf3f01bSAndreas Gohr { 432a2d649c4Sandi $this->doc .= '<del>'; 4330cecf9d5Sandi } 4340cecf9d5Sandi 4353dd5c225SAndreas Gohr /** 4363dd5c225SAndreas Gohr * Stop deleted (strike-through) formatting 4373dd5c225SAndreas Gohr */ 438faf3f01bSAndreas Gohr public function deleted_close() 439faf3f01bSAndreas Gohr { 440a2d649c4Sandi $this->doc .= '</del>'; 4410cecf9d5Sandi } 4420cecf9d5Sandi 4433fd0b676Sandi /** 4443fd0b676Sandi * Callback for footnote start syntax 4453fd0b676Sandi * 4463fd0b676Sandi * All following content will go to the footnote instead of 447d74aace9Schris * the document. To achieve this the previous rendered content 4483fd0b676Sandi * is moved to $store and $doc is cleared 4493fd0b676Sandi * 4503fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 4513fd0b676Sandi */ 452faf3f01bSAndreas Gohr public function footnote_open() 453faf3f01bSAndreas Gohr { 4547764a90aSandi 4557764a90aSandi // move current content to store and record footnote 4567764a90aSandi $this->store = $this->doc; 4577764a90aSandi $this->doc = ''; 4580cecf9d5Sandi } 4590cecf9d5Sandi 4603fd0b676Sandi /** 4613fd0b676Sandi * Callback for footnote end syntax 4623fd0b676Sandi * 4633fd0b676Sandi * All rendered content is moved to the $footnotes array and the old 4643fd0b676Sandi * content is restored from $store again 4653fd0b676Sandi * 4663fd0b676Sandi * @author Andreas Gohr 4673fd0b676Sandi */ 468faf3f01bSAndreas Gohr public function footnote_close() 469faf3f01bSAndreas Gohr { 47016ec3e37SAndreas Gohr /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 47116ec3e37SAndreas Gohr static $fnid = 0; 47216ec3e37SAndreas Gohr // assign new footnote id (we start at 1) 47316ec3e37SAndreas Gohr $fnid++; 4747764a90aSandi 475d74aace9Schris // recover footnote into the stack and restore old content 476d74aace9Schris $footnote = $this->doc; 4777764a90aSandi $this->doc = $this->store; 4787764a90aSandi $this->store = ''; 479d74aace9Schris 480d74aace9Schris // check to see if this footnote has been seen before 481d74aace9Schris $i = array_search($footnote, $this->footnotes); 482d74aace9Schris 483d74aace9Schris if ($i === false) { 484d74aace9Schris // its a new footnote, add it to the $footnotes array 48516ec3e37SAndreas Gohr $this->footnotes[$fnid] = $footnote; 486d74aace9Schris } else { 48716ec3e37SAndreas Gohr // seen this one before, save a placeholder 48816ec3e37SAndreas Gohr $this->footnotes[$fnid] = "@@FNT" . ($i); 489d74aace9Schris } 490d74aace9Schris 4916b379cbfSAndreas Gohr // output the footnote reference and link 49295078f23SAndreas Gohr $this->doc .= sprintf( 49395078f23SAndreas Gohr '<sup><a href="#fn__%d" id="fnt__%d" class="fn_top">%d)</a></sup>', 49495078f23SAndreas Gohr $fnid, 49595078f23SAndreas Gohr $fnid, 49695078f23SAndreas Gohr $fnid 49795078f23SAndreas Gohr ); 4980cecf9d5Sandi } 4990cecf9d5Sandi 5003dd5c225SAndreas Gohr /** 5013dd5c225SAndreas Gohr * Open an unordered list 5020c4c0281SGerrit Uitslag * 5037d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 5043dd5c225SAndreas Gohr */ 505faf3f01bSAndreas Gohr public function listu_open($classes = null) 506faf3f01bSAndreas Gohr { 5070c4c0281SGerrit Uitslag $class = ''; 5080c4c0281SGerrit Uitslag if ($classes !== null) { 509faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 5100c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 5110c4c0281SGerrit Uitslag } 5120c4c0281SGerrit Uitslag $this->doc .= "<ul$class>" . DOKU_LF; 5130cecf9d5Sandi } 5140cecf9d5Sandi 5153dd5c225SAndreas Gohr /** 5163dd5c225SAndreas Gohr * Close an unordered list 5173dd5c225SAndreas Gohr */ 518faf3f01bSAndreas Gohr public function listu_close() 519faf3f01bSAndreas Gohr { 520a2d649c4Sandi $this->doc .= '</ul>' . DOKU_LF; 5210cecf9d5Sandi } 5220cecf9d5Sandi 5233dd5c225SAndreas Gohr /** 5243dd5c225SAndreas Gohr * Open an ordered list 5250c4c0281SGerrit Uitslag * 5267d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 5273dd5c225SAndreas Gohr */ 528faf3f01bSAndreas Gohr public function listo_open($classes = null) 529faf3f01bSAndreas Gohr { 5300c4c0281SGerrit Uitslag $class = ''; 5310c4c0281SGerrit Uitslag if ($classes !== null) { 532faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 5330c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 5340c4c0281SGerrit Uitslag } 5350c4c0281SGerrit Uitslag $this->doc .= "<ol$class>" . DOKU_LF; 5360cecf9d5Sandi } 5370cecf9d5Sandi 5383dd5c225SAndreas Gohr /** 5393dd5c225SAndreas Gohr * Close an ordered list 5403dd5c225SAndreas Gohr */ 541faf3f01bSAndreas Gohr public function listo_close() 542faf3f01bSAndreas Gohr { 543a2d649c4Sandi $this->doc .= '</ol>' . DOKU_LF; 5440cecf9d5Sandi } 5450cecf9d5Sandi 5463dd5c225SAndreas Gohr /** 5473dd5c225SAndreas Gohr * Open a list item 5483dd5c225SAndreas Gohr * 5493dd5c225SAndreas Gohr * @param int $level the nesting level 550e3a24861SChristopher Smith * @param bool $node true when a node; false when a leaf 5513dd5c225SAndreas Gohr */ 552faf3f01bSAndreas Gohr public function listitem_open($level, $node = false) 553faf3f01bSAndreas Gohr { 554e3a24861SChristopher Smith $branching = $node ? ' node' : ''; 555e3a24861SChristopher Smith $this->doc .= '<li class="level' . $level . $branching . '">'; 5560cecf9d5Sandi } 5570cecf9d5Sandi 5583dd5c225SAndreas Gohr /** 5593dd5c225SAndreas Gohr * Close a list item 5603dd5c225SAndreas Gohr */ 561faf3f01bSAndreas Gohr public function listitem_close() 562faf3f01bSAndreas Gohr { 563a2d649c4Sandi $this->doc .= '</li>' . DOKU_LF; 5640cecf9d5Sandi } 5650cecf9d5Sandi 5663dd5c225SAndreas Gohr /** 5673dd5c225SAndreas Gohr * Start the content of a list item 5683dd5c225SAndreas Gohr */ 569faf3f01bSAndreas Gohr public function listcontent_open() 570faf3f01bSAndreas Gohr { 57190db23d7Schris $this->doc .= '<div class="li">'; 5720cecf9d5Sandi } 5730cecf9d5Sandi 5743dd5c225SAndreas Gohr /** 5753dd5c225SAndreas Gohr * Stop the content of a list item 5763dd5c225SAndreas Gohr */ 577faf3f01bSAndreas Gohr public function listcontent_close() 578faf3f01bSAndreas Gohr { 57959869a4bSAnika Henke $this->doc .= '</div>' . DOKU_LF; 5800cecf9d5Sandi } 5810cecf9d5Sandi 5823dd5c225SAndreas Gohr /** 5833dd5c225SAndreas Gohr * Output unformatted $text 5843dd5c225SAndreas Gohr * 5853dd5c225SAndreas Gohr * Defaults to $this->cdata() 5863dd5c225SAndreas Gohr * 5873dd5c225SAndreas Gohr * @param string $text 5883dd5c225SAndreas Gohr */ 589faf3f01bSAndreas Gohr public function unformatted($text) 590faf3f01bSAndreas Gohr { 591a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 5920cecf9d5Sandi } 5930cecf9d5Sandi 5940cecf9d5Sandi /** 5953dd5c225SAndreas Gohr * Start a block quote 5963dd5c225SAndreas Gohr */ 597faf3f01bSAndreas Gohr public function quote_open() 598faf3f01bSAndreas Gohr { 59996331712SAnika Henke $this->doc .= '<blockquote><div class="no">' . DOKU_LF; 6000cecf9d5Sandi } 6010cecf9d5Sandi 6023dd5c225SAndreas Gohr /** 6033dd5c225SAndreas Gohr * Stop a block quote 6043dd5c225SAndreas Gohr */ 605faf3f01bSAndreas Gohr public function quote_close() 606faf3f01bSAndreas Gohr { 60796331712SAnika Henke $this->doc .= '</div></blockquote>' . DOKU_LF; 6080cecf9d5Sandi } 6090cecf9d5Sandi 6103dd5c225SAndreas Gohr /** 6113dd5c225SAndreas Gohr * Output preformatted text 6123dd5c225SAndreas Gohr * 6133dd5c225SAndreas Gohr * @param string $text 6143dd5c225SAndreas Gohr */ 615faf3f01bSAndreas Gohr public function preformatted($text) 616faf3f01bSAndreas Gohr { 617c9250713SAnika Henke $this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text), "\n\r") . '</pre>' . DOKU_LF; 6183d491f75SAndreas Gohr } 6193d491f75SAndreas Gohr 6203dd5c225SAndreas Gohr /** 6213dd5c225SAndreas Gohr * Display text as file content, optionally syntax highlighted 6223dd5c225SAndreas Gohr * 6233dd5c225SAndreas Gohr * @param string $text text to show 6243dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6253dd5c225SAndreas Gohr * @param string $filename file path label 6260e11cf25Sbrzsmg * @param array $options associative array with additional geshi options 6273dd5c225SAndreas Gohr */ 628faf3f01bSAndreas Gohr public function file($text, $language = null, $filename = null, $options = null) 629faf3f01bSAndreas Gohr { 630e2d88156SLarsDW223 $this->_highlight('file', $text, $language, $filename, $options); 6313d491f75SAndreas Gohr } 6323d491f75SAndreas Gohr 6333dd5c225SAndreas Gohr /** 6343dd5c225SAndreas Gohr * Display text as code content, optionally syntax highlighted 6353dd5c225SAndreas Gohr * 6363dd5c225SAndreas Gohr * @param string $text text to show 6373dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6383dd5c225SAndreas Gohr * @param string $filename file path label 6390e11cf25Sbrzsmg * @param array $options associative array with additional geshi options 6403dd5c225SAndreas Gohr */ 641faf3f01bSAndreas Gohr public function code($text, $language = null, $filename = null, $options = null) 642faf3f01bSAndreas Gohr { 643e2d88156SLarsDW223 $this->_highlight('code', $text, $language, $filename, $options); 6443d491f75SAndreas Gohr } 6453d491f75SAndreas Gohr 6460cecf9d5Sandi /** 6473d491f75SAndreas Gohr * Use GeSHi to highlight language syntax in code and file blocks 6483fd0b676Sandi * 6493dd5c225SAndreas Gohr * @param string $type code|file 6503dd5c225SAndreas Gohr * @param string $text text to show 6513dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6523dd5c225SAndreas Gohr * @param string $filename file path label 6530e11cf25Sbrzsmg * @param array $options associative array with additional geshi options 654faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 6550cecf9d5Sandi */ 656faf3f01bSAndreas Gohr public function _highlight($type, $text, $language = null, $filename = null, $options = null) 657faf3f01bSAndreas Gohr { 6583d491f75SAndreas Gohr global $ID; 6593d491f75SAndreas Gohr global $lang; 660ec57f119SLarsDW223 global $INPUT; 6613d491f75SAndreas Gohr 662bf8f8509SAndreas Gohr $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language ?? ''); 66356bd9509SPhy 6643d491f75SAndreas Gohr if ($filename) { 665190c56e8SAndreas Gohr // add icon 666faf3f01bSAndreas Gohr [$ext] = mimetype($filename, false); 667190c56e8SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 668190c56e8SAndreas Gohr $class = 'mediafile mf_' . $class; 669190c56e8SAndreas Gohr 670ec57f119SLarsDW223 $offset = 0; 671ec57f119SLarsDW223 if ($INPUT->has('codeblockOffset')) { 672ec57f119SLarsDW223 $offset = $INPUT->str('codeblockOffset'); 673ec57f119SLarsDW223 } 6743d491f75SAndreas Gohr $this->doc .= '<dl class="' . $type . '">' . DOKU_LF; 67564159a61SAndreas Gohr $this->doc .= '<dt><a href="' . 67664159a61SAndreas Gohr exportlink( 67764159a61SAndreas Gohr $ID, 67864159a61SAndreas Gohr 'code', 679faf3f01bSAndreas Gohr ['codeblock' => $offset + $this->_codeblock] 68064159a61SAndreas Gohr ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; 6813d491f75SAndreas Gohr $this->doc .= hsc($filename); 6823d491f75SAndreas Gohr $this->doc .= '</a></dt>' . DOKU_LF . '<dd>'; 6833d491f75SAndreas Gohr } 6840cecf9d5Sandi 6856c16a3a9Sfiwswe if (str_starts_with($text, "\n")) { 686d43aac1cSGina Haeussge $text = substr($text, 1); 687d43aac1cSGina Haeussge } 6886c16a3a9Sfiwswe if (str_ends_with($text, "\n")) { 689d43aac1cSGina Haeussge $text = substr($text, 0, -1); 690d43aac1cSGina Haeussge } 691d43aac1cSGina Haeussge 692a056e285SPhy if (empty($language)) { // empty is faster than is_null and can prevent '' string 6933d491f75SAndreas Gohr $this->doc .= '<pre class="' . $type . '">' . $this->_xmlEntities($text) . '</pre>' . DOKU_LF; 6940cecf9d5Sandi } else { 6953d491f75SAndreas Gohr $class = 'code'; //we always need the code class to make the syntax highlighting apply 6963d491f75SAndreas Gohr if ($type != 'code') $class .= ' ' . $type; 6973d491f75SAndreas Gohr 69864159a61SAndreas Gohr $this->doc .= "<pre class=\"$class $language\">" . 69964159a61SAndreas Gohr p_xhtml_cached_geshi($text, $language, '', $options) . 70064159a61SAndreas Gohr '</pre>' . DOKU_LF; 7010cecf9d5Sandi } 7023d491f75SAndreas Gohr 7033d491f75SAndreas Gohr if ($filename) { 7043d491f75SAndreas Gohr $this->doc .= '</dd></dl>' . DOKU_LF; 7053d491f75SAndreas Gohr } 7063d491f75SAndreas Gohr 7073d491f75SAndreas Gohr $this->_codeblock++; 7080cecf9d5Sandi } 7090cecf9d5Sandi 7103dd5c225SAndreas Gohr /** 7113dd5c225SAndreas Gohr * Format an acronym 7123dd5c225SAndreas Gohr * 7133dd5c225SAndreas Gohr * Uses $this->acronyms 7143dd5c225SAndreas Gohr * 7153dd5c225SAndreas Gohr * @param string $acronym 7163dd5c225SAndreas Gohr */ 717faf3f01bSAndreas Gohr public function acronym($acronym) 718faf3f01bSAndreas Gohr { 7190cecf9d5Sandi 7200cecf9d5Sandi if (array_key_exists($acronym, $this->acronyms)) { 721433bef32Sandi $title = $this->_xmlEntities($this->acronyms[$acronym]); 7220cecf9d5Sandi 723940db3a3SAnika Henke $this->doc .= '<abbr title="' . $title 724940db3a3SAnika Henke . '">' . $this->_xmlEntities($acronym) . '</abbr>'; 7250cecf9d5Sandi } else { 726a2d649c4Sandi $this->doc .= $this->_xmlEntities($acronym); 7270cecf9d5Sandi } 7280cecf9d5Sandi } 7290cecf9d5Sandi 7303dd5c225SAndreas Gohr /** 7313dd5c225SAndreas Gohr * Format a smiley 7323dd5c225SAndreas Gohr * 7333dd5c225SAndreas Gohr * Uses $this->smiley 7343dd5c225SAndreas Gohr * 7353dd5c225SAndreas Gohr * @param string $smiley 7363dd5c225SAndreas Gohr */ 737faf3f01bSAndreas Gohr public function smiley($smiley) 738faf3f01bSAndreas Gohr { 739b09504a9SAndreas Gohr if (isset($this->smileys[$smiley])) { 740e44b94a4SAndreas Gohr $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] . 741b09504a9SAndreas Gohr '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />'; 7420cecf9d5Sandi } else { 743a2d649c4Sandi $this->doc .= $this->_xmlEntities($smiley); 7440cecf9d5Sandi } 7450cecf9d5Sandi } 7460cecf9d5Sandi 7473dd5c225SAndreas Gohr /** 7483dd5c225SAndreas Gohr * Format an entity 7493dd5c225SAndreas Gohr * 7503dd5c225SAndreas Gohr * Entities are basically small text replacements 7513dd5c225SAndreas Gohr * 7523dd5c225SAndreas Gohr * Uses $this->entities 7533dd5c225SAndreas Gohr * 7543dd5c225SAndreas Gohr * @param string $entity 7554de671bcSandi */ 756faf3f01bSAndreas Gohr public function entity($entity) 757faf3f01bSAndreas Gohr { 7580cecf9d5Sandi if (array_key_exists($entity, $this->entities)) { 759a2d649c4Sandi $this->doc .= $this->entities[$entity]; 7600cecf9d5Sandi } else { 761a2d649c4Sandi $this->doc .= $this->_xmlEntities($entity); 7620cecf9d5Sandi } 7630cecf9d5Sandi } 7640cecf9d5Sandi 7653dd5c225SAndreas Gohr /** 7663dd5c225SAndreas Gohr * Typographically format a multiply sign 7673dd5c225SAndreas Gohr * 7683dd5c225SAndreas Gohr * Example: ($x=640, $y=480) should result in "640×480" 7693dd5c225SAndreas Gohr * 7703dd5c225SAndreas Gohr * @param string|int $x first value 7713dd5c225SAndreas Gohr * @param string|int $y second value 7723dd5c225SAndreas Gohr */ 773faf3f01bSAndreas Gohr public function multiplyentity($x, $y) 774faf3f01bSAndreas Gohr { 775a2d649c4Sandi $this->doc .= "$x×$y"; 7760cecf9d5Sandi } 7770cecf9d5Sandi 7783dd5c225SAndreas Gohr /** 7793dd5c225SAndreas Gohr * Render an opening single quote char (language specific) 7803dd5c225SAndreas Gohr */ 781faf3f01bSAndreas Gohr public function singlequoteopening() 782faf3f01bSAndreas Gohr { 78371b40da2SAnika Henke global $lang; 78471b40da2SAnika Henke $this->doc .= $lang['singlequoteopening']; 7850cecf9d5Sandi } 7860cecf9d5Sandi 7873dd5c225SAndreas Gohr /** 7883dd5c225SAndreas Gohr * Render a closing single quote char (language specific) 7893dd5c225SAndreas Gohr */ 790faf3f01bSAndreas Gohr public function singlequoteclosing() 791faf3f01bSAndreas Gohr { 79271b40da2SAnika Henke global $lang; 79371b40da2SAnika Henke $this->doc .= $lang['singlequoteclosing']; 7940cecf9d5Sandi } 7950cecf9d5Sandi 7963dd5c225SAndreas Gohr /** 7973dd5c225SAndreas Gohr * Render an apostrophe char (language specific) 7983dd5c225SAndreas Gohr */ 799faf3f01bSAndreas Gohr public function apostrophe() 800faf3f01bSAndreas Gohr { 80157d757d1SAndreas Gohr global $lang; 802a8bd192aSAndreas Gohr $this->doc .= $lang['apostrophe']; 80357d757d1SAndreas Gohr } 80457d757d1SAndreas Gohr 8053dd5c225SAndreas Gohr /** 8063dd5c225SAndreas Gohr * Render an opening double quote char (language specific) 8073dd5c225SAndreas Gohr */ 808faf3f01bSAndreas Gohr public function doublequoteopening() 809faf3f01bSAndreas Gohr { 81071b40da2SAnika Henke global $lang; 81171b40da2SAnika Henke $this->doc .= $lang['doublequoteopening']; 8120cecf9d5Sandi } 8130cecf9d5Sandi 8143dd5c225SAndreas Gohr /** 8153dd5c225SAndreas Gohr * Render an closinging double quote char (language specific) 8163dd5c225SAndreas Gohr */ 817faf3f01bSAndreas Gohr public function doublequoteclosing() 818faf3f01bSAndreas Gohr { 81971b40da2SAnika Henke global $lang; 82071b40da2SAnika Henke $this->doc .= $lang['doublequoteclosing']; 8210cecf9d5Sandi } 8220cecf9d5Sandi 8230cecf9d5Sandi /** 8243dd5c225SAndreas Gohr * Render a CamelCase link 8253dd5c225SAndreas Gohr * 8263dd5c225SAndreas Gohr * @param string $link The link name 827122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8280c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8290c4c0281SGerrit Uitslag * 8303dd5c225SAndreas Gohr * @see http://en.wikipedia.org/wiki/CamelCase 8310cecf9d5Sandi */ 832faf3f01bSAndreas Gohr public function camelcaselink($link, $returnonly = false) 833faf3f01bSAndreas Gohr { 834122f2d46SAndreas Böhler if ($returnonly) { 835122f2d46SAndreas Böhler return $this->internallink($link, $link, null, true); 836122f2d46SAndreas Böhler } else { 83711d0aa47Sandi $this->internallink($link, $link); 8380cecf9d5Sandi } 839122f2d46SAndreas Böhler } 8400cecf9d5Sandi 8413dd5c225SAndreas Gohr /** 8423dd5c225SAndreas Gohr * Render a page local link 8433dd5c225SAndreas Gohr * 8443dd5c225SAndreas Gohr * @param string $hash hash link identifier 8453dd5c225SAndreas Gohr * @param string $name name for the link 846122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8470c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8483dd5c225SAndreas Gohr */ 849faf3f01bSAndreas Gohr public function locallink($hash, $name = null, $returnonly = false) 850faf3f01bSAndreas Gohr { 8510b7c14c2Sandi global $ID; 8520b7c14c2Sandi $name = $this->_getLinkTitle($name, $hash, $isImage); 8530b7c14c2Sandi $hash = $this->_headerToLink($hash); 854e260f93bSAnika Henke $title = $ID . ' ↵'; 855122f2d46SAndreas Böhler 856122f2d46SAndreas Böhler $doc = '<a href="#' . $hash . '" title="' . $title . '" class="wikilink1">'; 857122f2d46SAndreas Böhler $doc .= $name; 858122f2d46SAndreas Böhler $doc .= '</a>'; 859122f2d46SAndreas Böhler 860122f2d46SAndreas Böhler if ($returnonly) { 861122f2d46SAndreas Böhler return $doc; 862122f2d46SAndreas Böhler } else { 863122f2d46SAndreas Böhler $this->doc .= $doc; 864122f2d46SAndreas Böhler } 8650b7c14c2Sandi } 8660b7c14c2Sandi 867cffcc403Sandi /** 8683fd0b676Sandi * Render an internal Wiki Link 8693fd0b676Sandi * 870fe9ec250SChris Smith * $search,$returnonly & $linktype are not for the renderer but are used 871cffcc403Sandi * elsewhere - no need to implement them in other renderers 8723fd0b676Sandi * 873f23eef27SGerrit Uitslag * @param string $id pageid 874f23eef27SGerrit Uitslag * @param string|null $name link name 875f23eef27SGerrit Uitslag * @param string|null $search adds search url param 876f23eef27SGerrit Uitslag * @param bool $returnonly whether to return html or write to doc attribute 877f23eef27SGerrit Uitslag * @param string $linktype type to set use of headings 878f23eef27SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 879faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 880cffcc403Sandi */ 881faf3f01bSAndreas Gohr public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') 882faf3f01bSAndreas Gohr { 883ba11bd29Sandi global $conf; 88437e34a5eSandi global $ID; 885c4dda6afSAnika Henke global $INFO; 88644653a53SAdrian Lang 8873d5e07d9SAdrian Lang $params = ''; 8883d5e07d9SAdrian Lang $parts = explode('?', $id, 2); 8893d5e07d9SAdrian Lang if (count($parts) === 2) { 8903d5e07d9SAdrian Lang $id = $parts[0]; 8913d5e07d9SAdrian Lang $params = $parts[1]; 89244653a53SAdrian Lang } 89344653a53SAdrian Lang 894fda14ffcSIzidor Matušov // For empty $id we need to know the current $ID 895fda14ffcSIzidor Matušov // We need this check because _simpleTitle needs 896fda14ffcSIzidor Matušov // correct $id and resolve_pageid() use cleanID($id) 897fda14ffcSIzidor Matušov // (some things could be lost) 898fda14ffcSIzidor Matušov if ($id === '') { 899fda14ffcSIzidor Matušov $id = $ID; 900fda14ffcSIzidor Matušov } 901fda14ffcSIzidor Matušov 9020339c872Sjan // default name is based on $id as given 9030339c872Sjan $default = $this->_simpleTitle($id); 904ad32e47eSAndreas Gohr 9050339c872Sjan // now first resolve and clean up the $id 9068c6be208SAndreas Gohr $id = (new PageResolver($ID))->resolveId($id, $this->date_at, true); 9078c6be208SAndreas Gohr $exists = page_exists($id, $this->date_at, false, true); 908fda14ffcSIzidor Matušov 909faf3f01bSAndreas Gohr $link = []; 910fe9ec250SChris Smith $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 9110e1c636eSandi if (!$isImage) { 9120e1c636eSandi if ($exists) { 913ba11bd29Sandi $class = 'wikilink1'; 9140cecf9d5Sandi } else { 915ba11bd29Sandi $class = 'wikilink2'; 91644a6b4c7SAndreas Gohr $link['rel'] = 'nofollow'; 9170cecf9d5Sandi } 9180cecf9d5Sandi } else { 919ba11bd29Sandi $class = 'media'; 9200cecf9d5Sandi } 9210cecf9d5Sandi 922a1685bedSandi //keep hash anchor 923faf3f01bSAndreas Gohr [$id, $hash] = sexplode('#', $id, 2); 924943dedc6SAndreas Gohr if (!empty($hash)) $hash = $this->_headerToLink($hash); 925a1685bedSandi 926ba11bd29Sandi //prepare for formating 927ba11bd29Sandi $link['target'] = $conf['target']['wiki']; 928ba11bd29Sandi $link['style'] = ''; 929ba11bd29Sandi $link['pre'] = ''; 930ba11bd29Sandi $link['suf'] = ''; 931bbac1489SPhy $link['more'] = 'data-wiki-id="' . $id . '"'; // id is already cleaned 932ba11bd29Sandi $link['class'] = $class; 9335c2eed9aSlisps if ($this->date_at) { 934912a6d48SPhy $params = $params . '&at=' . rawurlencode($this->date_at); 9355c2eed9aSlisps } 93644653a53SAdrian Lang $link['url'] = wl($id, $params); 937ba11bd29Sandi $link['name'] = $name; 938ba11bd29Sandi $link['title'] = $id; 939723d78dbSandi //add search string 940723d78dbSandi if ($search) { 941546d3a99SAndreas Gohr ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 942546d3a99SAndreas Gohr if (is_array($search)) { 943093fe67eSAndreas Gohr $search = array_map(rawurlencode(...), $search); 944faf3f01bSAndreas Gohr $link['url'] .= 's[]=' . implode('&s[]=', $search); 945546d3a99SAndreas Gohr } else { 946546d3a99SAndreas Gohr $link['url'] .= 's=' . rawurlencode($search); 947546d3a99SAndreas Gohr } 948723d78dbSandi } 949723d78dbSandi 950a1685bedSandi //keep hash 951a1685bedSandi if ($hash) $link['url'] .= '#' . $hash; 952a1685bedSandi 953ba11bd29Sandi //output formatted 954cffcc403Sandi if ($returnonly) { 955cffcc403Sandi return $this->_formatLink($link); 956cffcc403Sandi } else { 957a2d649c4Sandi $this->doc .= $this->_formatLink($link); 9580cecf9d5Sandi } 959cffcc403Sandi } 9600cecf9d5Sandi 9613dd5c225SAndreas Gohr /** 9623dd5c225SAndreas Gohr * Render an external link 9633dd5c225SAndreas Gohr * 9643dd5c225SAndreas Gohr * @param string $url full URL with scheme 9653dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 966122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9670c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9683dd5c225SAndreas Gohr */ 969faf3f01bSAndreas Gohr public function externallink($url, $name = null, $returnonly = false) 970faf3f01bSAndreas Gohr { 971b625487dSandi global $conf; 9720cecf9d5Sandi 973433bef32Sandi $name = $this->_getLinkTitle($name, $url, $isImage); 9746f0c5dbfSandi 975b52b1596SAndreas Gohr // url might be an attack vector, only allow registered protocols 976b52b1596SAndreas Gohr if (is_null($this->schemes)) $this->schemes = getSchemes(); 977faf3f01bSAndreas Gohr [$scheme] = explode('://', $url); 978b52b1596SAndreas Gohr $scheme = strtolower($scheme); 979b52b1596SAndreas Gohr if (!in_array($scheme, $this->schemes)) $url = ''; 980b52b1596SAndreas Gohr 981b52b1596SAndreas Gohr // is there still an URL? 982b52b1596SAndreas Gohr if (!$url) { 98349cef4fdSAndreas Böhler if ($returnonly) { 98449cef4fdSAndreas Böhler return $name; 98549cef4fdSAndreas Böhler } else { 986b52b1596SAndreas Gohr $this->doc .= $name; 98749cef4fdSAndreas Böhler } 988b52b1596SAndreas Gohr return; 989b52b1596SAndreas Gohr } 990b52b1596SAndreas Gohr 991b52b1596SAndreas Gohr // set class 9920cecf9d5Sandi if (!$isImage) { 993b625487dSandi $class = 'urlextern'; 9940cecf9d5Sandi } else { 995b625487dSandi $class = 'media'; 9960cecf9d5Sandi } 9970cecf9d5Sandi 998b625487dSandi //prepare for formating 999faf3f01bSAndreas Gohr $link = []; 1000b625487dSandi $link['target'] = $conf['target']['extern']; 1001b625487dSandi $link['style'] = ''; 1002b625487dSandi $link['pre'] = ''; 1003b625487dSandi $link['suf'] = ''; 10045e163278SAndreas Gohr $link['more'] = ''; 1005b625487dSandi $link['class'] = $class; 1006b625487dSandi $link['url'] = $url; 1007914045f3SAndreas Gohr $link['rel'] = ''; 1008e1c10e4dSchris 1009b625487dSandi $link['name'] = $name; 1010433bef32Sandi $link['title'] = $this->_xmlEntities($url); 10115ddd0bbbSStarArmy if ($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; 1012914045f3SAndreas Gohr if ($conf['target']['extern']) $link['rel'] .= ' noopener'; 10130cecf9d5Sandi 1014b625487dSandi //output formatted 1015122f2d46SAndreas Böhler if ($returnonly) { 1016122f2d46SAndreas Böhler return $this->_formatLink($link); 1017122f2d46SAndreas Böhler } else { 1018a2d649c4Sandi $this->doc .= $this->_formatLink($link); 10190cecf9d5Sandi } 1020122f2d46SAndreas Böhler } 10210cecf9d5Sandi 10220cecf9d5Sandi /** 10233dd5c225SAndreas Gohr * Render an interwiki link 10243dd5c225SAndreas Gohr * 10253dd5c225SAndreas Gohr * You may want to use $this->_resolveInterWiki() here 10263dd5c225SAndreas Gohr * 10273dd5c225SAndreas Gohr * @param string $match original link - probably not much use 10283dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 10293dd5c225SAndreas Gohr * @param string $wikiName indentifier (shortcut) for the remote wiki 10303dd5c225SAndreas Gohr * @param string $wikiUri the fragment parsed from the original link 1031122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10320c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10330cecf9d5Sandi */ 1034faf3f01bSAndreas Gohr public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) 1035faf3f01bSAndreas Gohr { 1036b625487dSandi global $conf; 10370cecf9d5Sandi 1038faf3f01bSAndreas Gohr $link = []; 103997a3e4e3Sandi $link['target'] = $conf['target']['interwiki']; 104097a3e4e3Sandi $link['pre'] = ''; 104197a3e4e3Sandi $link['suf'] = ''; 10425e163278SAndreas Gohr $link['more'] = ''; 1043433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 1044914045f3SAndreas Gohr $link['rel'] = ''; 10450cecf9d5Sandi 104697a3e4e3Sandi //get interwiki URL 10476496c33fSGerrit Uitslag $exists = null; 10486496c33fSGerrit Uitslag $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 10490cecf9d5Sandi 105097a3e4e3Sandi if (!$isImage) { 10519d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 10529d2ddea4SAndreas Gohr $link['class'] = "interwiki iw_$class"; 10531c2d1019SAndreas Gohr } else { 10541c2d1019SAndreas Gohr $link['class'] = 'media'; 105597a3e4e3Sandi } 10560cecf9d5Sandi 105797a3e4e3Sandi //do we stay at the same server? Use local target 1058093fe67eSAndreas Gohr if (str_starts_with($url, DOKU_URL) || str_starts_with($url, DOKU_BASE)) { 105997a3e4e3Sandi $link['target'] = $conf['target']['wiki']; 106097a3e4e3Sandi } 10616496c33fSGerrit Uitslag if ($exists !== null && !$isImage) { 10626496c33fSGerrit Uitslag if ($exists) { 10636496c33fSGerrit Uitslag $link['class'] .= ' wikilink1'; 10646496c33fSGerrit Uitslag } else { 10656496c33fSGerrit Uitslag $link['class'] .= ' wikilink2'; 1066914045f3SAndreas Gohr $link['rel'] .= ' nofollow'; 10676496c33fSGerrit Uitslag } 10686496c33fSGerrit Uitslag } 1069914045f3SAndreas Gohr if ($conf['target']['interwiki']) $link['rel'] .= ' noopener'; 10700cecf9d5Sandi 107197a3e4e3Sandi $link['url'] = $url; 1072f7711f2bSAndreas Gohr $link['title'] = $this->_xmlEntities($link['url']); 107397a3e4e3Sandi 107497a3e4e3Sandi // output formatted 1075122f2d46SAndreas Böhler if ($returnonly) { 1076abde5980SPhy if ($url == '') return $link['name']; 1077122f2d46SAndreas Böhler return $this->_formatLink($link); 1078faf3f01bSAndreas Gohr } elseif ($url == '') { 1079faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1080faf3f01bSAndreas Gohr } else $this->doc .= $this->_formatLink($link); 1081122f2d46SAndreas Böhler } 10820cecf9d5Sandi 10830cecf9d5Sandi /** 10843dd5c225SAndreas Gohr * Link to windows share 10853dd5c225SAndreas Gohr * 10863dd5c225SAndreas Gohr * @param string $url the link 10873dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1088122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10890c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10900cecf9d5Sandi */ 1091faf3f01bSAndreas Gohr public function windowssharelink($url, $name = null, $returnonly = false) 1092faf3f01bSAndreas Gohr { 10931d47afe1Sandi global $conf; 10943dd5c225SAndreas Gohr 10951d47afe1Sandi //simple setup 1096faf3f01bSAndreas Gohr $link = []; 10971d47afe1Sandi $link['target'] = $conf['target']['windows']; 10981d47afe1Sandi $link['pre'] = ''; 10991d47afe1Sandi $link['suf'] = ''; 11001d47afe1Sandi $link['style'] = ''; 11010cecf9d5Sandi 1102433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 11030cecf9d5Sandi if (!$isImage) { 11041d47afe1Sandi $link['class'] = 'windows'; 11050cecf9d5Sandi } else { 11061d47afe1Sandi $link['class'] = 'media'; 11070cecf9d5Sandi } 11080cecf9d5Sandi 1109433bef32Sandi $link['title'] = $this->_xmlEntities($url); 11101d47afe1Sandi $url = str_replace('\\', '/', $url); 11111d47afe1Sandi $url = 'file:///' . $url; 11121d47afe1Sandi $link['url'] = $url; 11130cecf9d5Sandi 11141d47afe1Sandi //output formatted 1115122f2d46SAndreas Böhler if ($returnonly) { 1116122f2d46SAndreas Böhler return $this->_formatLink($link); 1117122f2d46SAndreas Böhler } else { 1118a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11190cecf9d5Sandi } 1120122f2d46SAndreas Böhler } 11210cecf9d5Sandi 11223dd5c225SAndreas Gohr /** 11233dd5c225SAndreas Gohr * Render a linked E-Mail Address 11243dd5c225SAndreas Gohr * 11253dd5c225SAndreas Gohr * Honors $conf['mailguard'] setting 11263dd5c225SAndreas Gohr * 11273dd5c225SAndreas Gohr * @param string $address Email-Address 11283dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1129122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 11300c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 11313dd5c225SAndreas Gohr */ 1132faf3f01bSAndreas Gohr public function emaillink($address, $name = null, $returnonly = false) 1133faf3f01bSAndreas Gohr { 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 1149*73dc0a89SAndreas Gohr $display = MailUtils::obfuscate($address); 1150*73dc0a89SAndreas Gohr $href = MailUtils::obfuscateUrl($address); 1151faf3f01bSAndreas Gohr 1152*73dc0a89SAndreas Gohr $title = $display; 11538c128049SAndreas Gohr 115471352defSandi if (empty($name)) { 1155*73dc0a89SAndreas Gohr $name = $display; 115671352defSandi } 11570cecf9d5Sandi 1158*73dc0a89SAndreas Gohr $link['url'] = 'mailto:' . $href; 115971352defSandi $link['name'] = $name; 116071352defSandi $link['title'] = $title; 11610cecf9d5Sandi 116271352defSandi //output formatted 1163122f2d46SAndreas Böhler if ($returnonly) { 1164122f2d46SAndreas Böhler return $this->_formatLink($link); 1165122f2d46SAndreas Böhler } else { 1166a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11670cecf9d5Sandi } 1168122f2d46SAndreas Böhler } 11690cecf9d5Sandi 11703dd5c225SAndreas Gohr /** 11713dd5c225SAndreas Gohr * Render an internal media file 11723dd5c225SAndreas Gohr * 11733dd5c225SAndreas Gohr * @param string $src media ID 11743dd5c225SAndreas Gohr * @param string $title descriptive text 11753dd5c225SAndreas Gohr * @param string $align left|center|right 11763dd5c225SAndreas Gohr * @param int $width width of media in pixel 11773dd5c225SAndreas Gohr * @param int $height height of media in pixel 11783dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11793dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 11803dd5c225SAndreas Gohr * @param bool $return return HTML instead of adding to $doc 11810c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11823dd5c225SAndreas Gohr */ 118395078f23SAndreas Gohr public function internalmedia( 118495078f23SAndreas Gohr $src, 118595078f23SAndreas Gohr $title = null, 118695078f23SAndreas Gohr $align = null, 118795078f23SAndreas Gohr $width = null, 118895078f23SAndreas Gohr $height = null, 118995078f23SAndreas Gohr $cache = null, 119095078f23SAndreas Gohr $linking = null, 119195078f23SAndreas Gohr $return = false 119295078f23SAndreas Gohr ) { 119337e34a5eSandi global $ID; 1194093fe67eSAndreas Gohr if (str_contains($src, '#')) { 1195faf3f01bSAndreas Gohr [$src, $hash] = sexplode('#', $src, 2); 11968f34cf3dSMichael Große } 11978c6be208SAndreas Gohr $src = (new MediaResolver($ID))->resolveId($src, $this->date_at, true); 11988c6be208SAndreas Gohr $exists = media_exists($src); 11990cecf9d5Sandi 1200d98d4540SBen Coburn $noLink = false; 1201faf3f01bSAndreas Gohr $render = $linking != 'linkonly'; 1202b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 12033685f775Sandi 1204faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src, false); 12056c16a3a9Sfiwswe if (str_starts_with($mime, 'image') && $render) { 120664159a61SAndreas Gohr $link['url'] = ml( 120764159a61SAndreas Gohr $src, 1208faf3f01bSAndreas Gohr [ 120964159a61SAndreas Gohr 'id' => $ID, 121064159a61SAndreas Gohr 'cache' => $cache, 121164159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1212faf3f01bSAndreas Gohr ], 121364159a61SAndreas Gohr ($linking == 'direct') 121464159a61SAndreas Gohr ); 1215f50634f0SAnika Henke } elseif (($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12162a2a2ba2SAnika Henke // don't link movies 121744881bd0Shenning.noren $noLink = true; 121855efc227SAndreas Gohr } else { 12192ca14335SEsther Brunner // add file icons 12209d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 12219d2ddea4SAndreas Gohr $link['class'] .= ' mediafile mf_' . $class; 122264159a61SAndreas Gohr $link['url'] = ml( 122364159a61SAndreas Gohr $src, 1224faf3f01bSAndreas Gohr [ 122564159a61SAndreas Gohr 'id' => $ID, 122664159a61SAndreas Gohr 'cache' => $cache, 122764159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1228faf3f01bSAndreas Gohr ], 122964159a61SAndreas Gohr true 123064159a61SAndreas Gohr ); 123191328684SMichael Hamann if ($exists) $link['title'] .= ' (' . filesize_h(filesize(mediaFN($src))) . ')'; 123255efc227SAndreas Gohr } 12333685f775Sandi 12348f34cf3dSMichael Große if (!empty($hash)) $link['url'] .= '#' . $hash; 123591df343aSAndreas Gohr 12366fe20453SGina Haeussge //markup non existing files 12374a24b459SKate Arzamastseva if (!$exists) { 12386fe20453SGina Haeussge $link['class'] .= ' wikilink2'; 12394a24b459SKate Arzamastseva } 12406fe20453SGina Haeussge 12413685f775Sandi //output formatted 1242f50634f0SAnika Henke if ($return) { 1243faf3f01bSAndreas Gohr if ($linking == 'nolink' || $noLink) { 1244faf3f01bSAndreas Gohr return $link['name']; 1245f50634f0SAnika Henke } else { 1246faf3f01bSAndreas Gohr return $this->_formatLink($link); 1247faf3f01bSAndreas Gohr } 1248faf3f01bSAndreas Gohr } elseif ($linking == 'nolink' || $noLink) { 1249faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1250faf3f01bSAndreas Gohr } else { 1251faf3f01bSAndreas Gohr $this->doc .= $this->_formatLink($link); 12520cecf9d5Sandi } 1253f50634f0SAnika Henke } 12540cecf9d5Sandi 12553dd5c225SAndreas Gohr /** 12563dd5c225SAndreas Gohr * Render an external media file 12573dd5c225SAndreas Gohr * 12583dd5c225SAndreas Gohr * @param string $src full media URL 12593dd5c225SAndreas Gohr * @param string $title descriptive text 12603dd5c225SAndreas Gohr * @param string $align left|center|right 12613dd5c225SAndreas Gohr * @param int $width width of media in pixel 12623dd5c225SAndreas Gohr * @param int $height height of media in pixel 12633dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 12643dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 1265410ee62aSAnika Henke * @param bool $return return HTML instead of adding to $doc 12660c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 12673dd5c225SAndreas Gohr */ 126895078f23SAndreas Gohr public function externalmedia( 126995078f23SAndreas Gohr $src, 127095078f23SAndreas Gohr $title = null, 127195078f23SAndreas Gohr $align = null, 127295078f23SAndreas Gohr $width = null, 127395078f23SAndreas Gohr $height = null, 127495078f23SAndreas Gohr $cache = null, 127595078f23SAndreas Gohr $linking = null, 127695078f23SAndreas Gohr $return = false 127795078f23SAndreas Gohr ) { 12786efc45a2SDmitry Katsubo if (link_isinterwiki($src)) { 1279faf3f01bSAndreas Gohr [$shortcut, $reference] = sexplode('>', $src, 2, ''); 12806efc45a2SDmitry Katsubo $exists = null; 12816efc45a2SDmitry Katsubo $src = $this->_resolveInterWiki($shortcut, $reference, $exists); 1282abde5980SPhy if ($src == '' && empty($title)) { 1283abde5980SPhy // make sure at least something will be shown in this case 1284abde5980SPhy $title = $reference; 1285abde5980SPhy } 12866efc45a2SDmitry Katsubo } 1287faf3f01bSAndreas Gohr [$src, $hash] = sexplode('#', $src, 2); 1288d98d4540SBen Coburn $noLink = false; 1289abde5980SPhy if ($src == '') { 1290abde5980SPhy // only output plaintext without link if there is no src 1291abde5980SPhy $noLink = true; 1292abde5980SPhy } 1293faf3f01bSAndreas Gohr $render = $linking != 'linkonly'; 1294b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1295b739ff0fSPierre Spring 1296faf3f01bSAndreas Gohr $link['url'] = ml($src, ['cache' => $cache]); 12973685f775Sandi 1298faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src, false); 12996c16a3a9Sfiwswe if (str_starts_with($mime, 'image') && $render) { 13002ca14335SEsther Brunner // link only jpeg images 130144881bd0Shenning.noren // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1302f50634f0SAnika Henke } elseif (($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 13032a2a2ba2SAnika Henke // don't link movies 130444881bd0Shenning.noren $noLink = true; 13052ca14335SEsther Brunner } else { 13062ca14335SEsther Brunner // add file icons 130727bf7924STom N Harris $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 130827bf7924STom N Harris $link['class'] .= ' mediafile mf_' . $class; 13092ca14335SEsther Brunner } 13102ca14335SEsther Brunner 131191df343aSAndreas Gohr if ($hash) $link['url'] .= '#' . $hash; 131291df343aSAndreas Gohr 13133685f775Sandi //output formatted 1314410ee62aSAnika Henke if ($return) { 1315410ee62aSAnika Henke if ($linking == 'nolink' || $noLink) return $link['name']; 1316410ee62aSAnika Henke else return $this->_formatLink($link); 1317faf3f01bSAndreas Gohr } elseif ($linking == 'nolink' || $noLink) { 1318faf3f01bSAndreas Gohr $this->doc .= $link['name']; 1319faf3f01bSAndreas Gohr } else $this->doc .= $this->_formatLink($link); 1320410ee62aSAnika Henke } 13210cecf9d5Sandi 13224826ab45Sandi /** 13233db95becSAndreas Gohr * Renders an RSS feed 1324b625487dSandi * 13250c4c0281SGerrit Uitslag * @param string $url URL of the feed 13260c4c0281SGerrit Uitslag * @param array $params Finetuning of the output 13270c4c0281SGerrit Uitslag * 1328b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 1329b625487dSandi */ 1330faf3f01bSAndreas Gohr public function rss($url, $params) 1331faf3f01bSAndreas Gohr { 1332b625487dSandi global $lang; 13333db95becSAndreas Gohr global $conf; 13343db95becSAndreas Gohr 13353db95becSAndreas Gohr $feed = new FeedParser(); 133600077af8SAndreas Gohr $feed->set_feed_url($url); 1337b625487dSandi 1338b625487dSandi //disable warning while fetching 13393dd5c225SAndreas Gohr if (!defined('DOKU_E_LEVEL')) { 13403dd5c225SAndreas Gohr $elvl = error_reporting(E_ERROR); 13413dd5c225SAndreas Gohr } 13423db95becSAndreas Gohr $rc = $feed->init(); 13433dd5c225SAndreas Gohr if (isset($elvl)) { 13443dd5c225SAndreas Gohr error_reporting($elvl); 13453dd5c225SAndreas Gohr } 1346b625487dSandi 134738c6f603SRobin H. Johnson if ($params['nosort']) $feed->enable_order_by_date(false); 134838c6f603SRobin H. Johnson 13493db95becSAndreas Gohr //decide on start and end 13503db95becSAndreas Gohr if ($params['reverse']) { 13513db95becSAndreas Gohr $mod = -1; 13523db95becSAndreas Gohr $start = $feed->get_item_quantity() - 1; 13533db95becSAndreas Gohr $end = $start - ($params['max']); 1354b2a412b0SAndreas Gohr $end = ($end < -1) ? -1 : $end; 13553db95becSAndreas Gohr } else { 13563db95becSAndreas Gohr $mod = 1; 13573db95becSAndreas Gohr $start = 0; 13583db95becSAndreas Gohr $end = $feed->get_item_quantity(); 1359d91ab76fSMatt Perry $end = ($end > $params['max']) ? $params['max'] : $end; 13603db95becSAndreas Gohr } 13613db95becSAndreas Gohr 1362a2d649c4Sandi $this->doc .= '<ul class="rss">'; 13633db95becSAndreas Gohr if ($rc) { 13643db95becSAndreas Gohr for ($x = $start; $x != $end; $x += $mod) { 13651bde1582SAndreas Gohr $item = $feed->get_item($x); 13663db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 136753df38b0SAndreas Gohr 1368d2ea3363SAndreas Gohr $lnkurl = $item->get_permalink(); 136953df38b0SAndreas Gohr $title = html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8'); 137053df38b0SAndreas Gohr 137153df38b0SAndreas Gohr // support feeds without links 1372d2ea3363SAndreas Gohr if ($lnkurl) { 137353df38b0SAndreas Gohr $this->externallink($item->get_permalink(), $title); 1374d2ea3363SAndreas Gohr } else { 137553df38b0SAndreas Gohr $this->doc .= ' ' . hsc($item->get_title()); 1376d2ea3363SAndreas Gohr } 13773db95becSAndreas Gohr if ($params['author']) { 13781bde1582SAndreas Gohr $author = $item->get_author(0); 1379faf3f01bSAndreas Gohr if ($author instanceof Author) { 13801bde1582SAndreas Gohr $name = $author->get_name(); 13811bde1582SAndreas Gohr if (!$name) $name = $author->get_email(); 1382163c2842SPhy if ($name) $this->doc .= ' ' . $lang['by'] . ' ' . hsc($name); 13831bde1582SAndreas Gohr } 13843db95becSAndreas Gohr } 13853db95becSAndreas Gohr if ($params['date']) { 13862e7e0c29SAndreas Gohr $this->doc .= ' (' . $item->get_local_date($conf['dformat']) . ')'; 13873db95becSAndreas Gohr } 13881bde1582SAndreas Gohr if ($params['details']) { 138953df38b0SAndreas Gohr $desc = $item->get_description(); 139053df38b0SAndreas Gohr $desc = strip_tags($desc); 139153df38b0SAndreas Gohr $desc = html_entity_decode($desc, ENT_QUOTES, 'UTF-8'); 13923db95becSAndreas Gohr $this->doc .= '<div class="detail">'; 139353df38b0SAndreas Gohr $this->doc .= hsc($desc); 13943db95becSAndreas Gohr $this->doc .= '</div>'; 13953db95becSAndreas Gohr } 13963db95becSAndreas Gohr 13973db95becSAndreas Gohr $this->doc .= '</div></li>'; 1398b625487dSandi } 1399b625487dSandi } else { 14003db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1401a2d649c4Sandi $this->doc .= '<em>' . $lang['rssfailed'] . '</em>'; 1402b625487dSandi $this->externallink($url); 140345e147ccSAndreas Gohr if ($conf['allowdebug']) { 140445e147ccSAndreas Gohr $this->doc .= '<!--' . hsc($feed->error) . '-->'; 140545e147ccSAndreas Gohr } 14063db95becSAndreas Gohr $this->doc .= '</div></li>'; 1407b625487dSandi } 1408a2d649c4Sandi $this->doc .= '</ul>'; 1409b625487dSandi } 1410b625487dSandi 14113dd5c225SAndreas Gohr /** 14123dd5c225SAndreas Gohr * Start a table 14133dd5c225SAndreas Gohr * 14143dd5c225SAndreas Gohr * @param int $maxcols maximum number of columns 14153dd5c225SAndreas Gohr * @param int $numrows NOT IMPLEMENTED 14163dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14177d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14183dd5c225SAndreas Gohr */ 1419faf3f01bSAndreas Gohr public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) 1420faf3f01bSAndreas Gohr { 1421b5742cedSPierre Spring // initialize the row counter used for classes 1422b5742cedSPierre Spring $this->_counter['row_counter'] = 0; 1423619736fdSAdrian Lang $class = 'table'; 14240c4c0281SGerrit Uitslag if ($classes !== null) { 1425faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 14260c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14270c4c0281SGerrit Uitslag } 1428619736fdSAdrian Lang if ($pos !== null) { 142906917fceSMichael Große $hid = $this->_headerToLink($class, true); 1430faf3f01bSAndreas Gohr $data = []; 1431ec57f119SLarsDW223 $data['target'] = 'table'; 1432ec57f119SLarsDW223 $data['name'] = ''; 1433ec57f119SLarsDW223 $data['hid'] = $hid; 1434ec57f119SLarsDW223 $class .= ' ' . $this->startSectionEdit($pos, $data); 1435619736fdSAdrian Lang } 1436619736fdSAdrian Lang $this->doc .= '<div class="' . $class . '"><table class="inline">' . 1437619736fdSAdrian Lang DOKU_LF; 14380cecf9d5Sandi } 14390cecf9d5Sandi 14403dd5c225SAndreas Gohr /** 14413dd5c225SAndreas Gohr * Close a table 14423dd5c225SAndreas Gohr * 14433dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14443dd5c225SAndreas Gohr */ 1445faf3f01bSAndreas Gohr public function table_close($pos = null) 1446faf3f01bSAndreas Gohr { 1447a8574918SAnika Henke $this->doc .= '</table></div>' . DOKU_LF; 1448619736fdSAdrian Lang if ($pos !== null) { 144990df9a4dSAdrian Lang $this->finishSectionEdit($pos); 14500cecf9d5Sandi } 1451619736fdSAdrian Lang } 14520cecf9d5Sandi 14533dd5c225SAndreas Gohr /** 14543dd5c225SAndreas Gohr * Open a table header 14553dd5c225SAndreas Gohr */ 1456faf3f01bSAndreas Gohr public function tablethead_open() 1457faf3f01bSAndreas Gohr { 1458f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB . '<thead>' . DOKU_LF; 1459f05a1cc5SGerrit Uitslag } 1460f05a1cc5SGerrit Uitslag 14613dd5c225SAndreas Gohr /** 14623dd5c225SAndreas Gohr * Close a table header 14633dd5c225SAndreas Gohr */ 1464faf3f01bSAndreas Gohr public function tablethead_close() 1465faf3f01bSAndreas Gohr { 1466f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB . '</thead>' . DOKU_LF; 1467f05a1cc5SGerrit Uitslag } 1468f05a1cc5SGerrit Uitslag 14693dd5c225SAndreas Gohr /** 14705a93f869SAnika Henke * Open a table body 14715a93f869SAnika Henke */ 1472faf3f01bSAndreas Gohr public function tabletbody_open() 1473faf3f01bSAndreas Gohr { 14745a93f869SAnika Henke $this->doc .= DOKU_TAB . '<tbody>' . DOKU_LF; 14755a93f869SAnika Henke } 14765a93f869SAnika Henke 14775a93f869SAnika Henke /** 14785a93f869SAnika Henke * Close a table body 14795a93f869SAnika Henke */ 1480faf3f01bSAndreas Gohr public function tabletbody_close() 1481faf3f01bSAndreas Gohr { 14825a93f869SAnika Henke $this->doc .= DOKU_TAB . '</tbody>' . DOKU_LF; 14835a93f869SAnika Henke } 14845a93f869SAnika Henke 14855a93f869SAnika Henke /** 1486d2a99739SAndreas Gohr * Open a table footer 1487d2a99739SAndreas Gohr */ 1488faf3f01bSAndreas Gohr public function tabletfoot_open() 1489faf3f01bSAndreas Gohr { 149044f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB . '<tfoot>' . DOKU_LF; 1491d2a99739SAndreas Gohr } 1492d2a99739SAndreas Gohr 1493d2a99739SAndreas Gohr /** 1494d2a99739SAndreas Gohr * Close a table footer 1495d2a99739SAndreas Gohr */ 1496faf3f01bSAndreas Gohr public function tabletfoot_close() 1497faf3f01bSAndreas Gohr { 149844f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB . '</tfoot>' . DOKU_LF; 1499d2a99739SAndreas Gohr } 1500d2a99739SAndreas Gohr 1501d2a99739SAndreas Gohr /** 15023dd5c225SAndreas Gohr * Open a table row 15030c4c0281SGerrit Uitslag * 15047d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15053dd5c225SAndreas Gohr */ 1506faf3f01bSAndreas Gohr public function tablerow_open($classes = null) 1507faf3f01bSAndreas Gohr { 1508b5742cedSPierre Spring // initialize the cell counter used for classes 1509b5742cedSPierre Spring $this->_counter['cell_counter'] = 0; 1510b5742cedSPierre Spring $class = 'row' . $this->_counter['row_counter']++; 15110c4c0281SGerrit Uitslag if ($classes !== null) { 1512faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15130c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15140c4c0281SGerrit Uitslag } 1515b5742cedSPierre Spring $this->doc .= DOKU_TAB . '<tr class="' . $class . '">' . DOKU_LF . DOKU_TAB . DOKU_TAB; 15160cecf9d5Sandi } 15170cecf9d5Sandi 15183dd5c225SAndreas Gohr /** 15193dd5c225SAndreas Gohr * Close a table row 15203dd5c225SAndreas Gohr */ 1521faf3f01bSAndreas Gohr public function tablerow_close() 1522faf3f01bSAndreas Gohr { 1523a2d649c4Sandi $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 15240cecf9d5Sandi } 15250cecf9d5Sandi 15263dd5c225SAndreas Gohr /** 15273dd5c225SAndreas Gohr * Open a table header cell 15283dd5c225SAndreas Gohr * 15293dd5c225SAndreas Gohr * @param int $colspan 15303dd5c225SAndreas Gohr * @param string $align left|center|right 15313dd5c225SAndreas Gohr * @param int $rowspan 15327d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15333dd5c225SAndreas Gohr */ 1534faf3f01bSAndreas Gohr public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) 1535faf3f01bSAndreas Gohr { 1536b5742cedSPierre Spring $class = 'class="col' . $this->_counter['cell_counter']++; 15370cecf9d5Sandi if (!is_null($align)) { 1538b5742cedSPierre Spring $class .= ' ' . $align . 'align'; 15390cecf9d5Sandi } 15400c4c0281SGerrit Uitslag if ($classes !== null) { 1541faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15420c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15430c4c0281SGerrit Uitslag } 1544b5742cedSPierre Spring $class .= '"'; 1545b5742cedSPierre Spring $this->doc .= '<th ' . $class; 15460cecf9d5Sandi if ($colspan > 1) { 1547a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1548a2d649c4Sandi $this->doc .= ' colspan="' . $colspan . '"'; 15490cecf9d5Sandi } 155025b97867Shakan.sandell if ($rowspan > 1) { 155125b97867Shakan.sandell $this->doc .= ' rowspan="' . $rowspan . '"'; 155225b97867Shakan.sandell } 1553a2d649c4Sandi $this->doc .= '>'; 15540cecf9d5Sandi } 15550cecf9d5Sandi 15563dd5c225SAndreas Gohr /** 15573dd5c225SAndreas Gohr * Close a table header cell 15583dd5c225SAndreas Gohr */ 1559faf3f01bSAndreas Gohr public function tableheader_close() 1560faf3f01bSAndreas Gohr { 1561a2d649c4Sandi $this->doc .= '</th>'; 15620cecf9d5Sandi } 15630cecf9d5Sandi 15643dd5c225SAndreas Gohr /** 15653dd5c225SAndreas Gohr * Open a table cell 15663dd5c225SAndreas Gohr * 15673dd5c225SAndreas Gohr * @param int $colspan 15683dd5c225SAndreas Gohr * @param string $align left|center|right 15693dd5c225SAndreas Gohr * @param int $rowspan 15707d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15713dd5c225SAndreas Gohr */ 1572faf3f01bSAndreas Gohr public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) 1573faf3f01bSAndreas Gohr { 1574b5742cedSPierre Spring $class = 'class="col' . $this->_counter['cell_counter']++; 15750cecf9d5Sandi if (!is_null($align)) { 1576b5742cedSPierre Spring $class .= ' ' . $align . 'align'; 15770cecf9d5Sandi } 15780c4c0281SGerrit Uitslag if ($classes !== null) { 1579faf3f01bSAndreas Gohr if (is_array($classes)) $classes = implode(' ', $classes); 15800c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15810c4c0281SGerrit Uitslag } 1582b5742cedSPierre Spring $class .= '"'; 1583b5742cedSPierre Spring $this->doc .= '<td ' . $class; 15840cecf9d5Sandi if ($colspan > 1) { 1585a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1586a2d649c4Sandi $this->doc .= ' colspan="' . $colspan . '"'; 15870cecf9d5Sandi } 158825b97867Shakan.sandell if ($rowspan > 1) { 158925b97867Shakan.sandell $this->doc .= ' rowspan="' . $rowspan . '"'; 159025b97867Shakan.sandell } 1591a2d649c4Sandi $this->doc .= '>'; 15920cecf9d5Sandi } 15930cecf9d5Sandi 15943dd5c225SAndreas Gohr /** 15953dd5c225SAndreas Gohr * Close a table cell 15963dd5c225SAndreas Gohr */ 1597faf3f01bSAndreas Gohr public function tablecell_close() 1598faf3f01bSAndreas Gohr { 1599a2d649c4Sandi $this->doc .= '</td>'; 16000cecf9d5Sandi } 16010cecf9d5Sandi 1602cea664bdSLarsDW223 /** 1603cea664bdSLarsDW223 * Returns the current header level. 1604cea664bdSLarsDW223 * (required e.g. by the filelist plugin) 1605cea664bdSLarsDW223 * 1606cea664bdSLarsDW223 * @return int The current header level 1607cea664bdSLarsDW223 */ 1608faf3f01bSAndreas Gohr public function getLastlevel() 1609faf3f01bSAndreas Gohr { 1610cea664bdSLarsDW223 return $this->lastlevel; 1611cea664bdSLarsDW223 } 1612cea664bdSLarsDW223 16133dd5c225SAndreas Gohr #region Utility functions 16140cecf9d5Sandi 1615ba11bd29Sandi /** 16163fd0b676Sandi * Build a link 16173fd0b676Sandi * 16183fd0b676Sandi * Assembles all parts defined in $link returns HTML for the link 1619ba11bd29Sandi * 16200c4c0281SGerrit Uitslag * @param array $link attributes of a link 16210c4c0281SGerrit Uitslag * @return string 16220c4c0281SGerrit Uitslag * 1623ba11bd29Sandi * @author Andreas Gohr <andi@splitbrain.org> 1624ba11bd29Sandi */ 1625faf3f01bSAndreas Gohr public function _formatLink($link) 1626faf3f01bSAndreas Gohr { 1627ba11bd29Sandi //make sure the url is XHTML compliant (skip mailto) 16286c16a3a9Sfiwswe if (!str_starts_with($link['url'], 'mailto:')) { 1629ba11bd29Sandi $link['url'] = str_replace('&', '&', $link['url']); 1630ba11bd29Sandi $link['url'] = str_replace('&amp;', '&', $link['url']); 1631ba11bd29Sandi } 1632ba11bd29Sandi //remove double encodings in titles 1633ba11bd29Sandi $link['title'] = str_replace('&amp;', '&', $link['title']); 1634ba11bd29Sandi 1635453493f2SAndreas Gohr // be sure there are no bad chars in url or title 1636453493f2SAndreas Gohr // (we can't do this for name because it can contain an img tag) 1637faf3f01bSAndreas Gohr $link['url'] = strtr($link['url'], ['>' => '%3E', '<' => '%3C', '"' => '%22']); 1638faf3f01bSAndreas Gohr $link['title'] = strtr($link['title'], ['>' => '>', '<' => '<', '"' => '"']); 1639453493f2SAndreas Gohr 1640ba11bd29Sandi $ret = ''; 1641ba11bd29Sandi $ret .= $link['pre']; 1642ba11bd29Sandi $ret .= '<a href="' . $link['url'] . '"'; 1643bb4866bdSchris if (!empty($link['class'])) $ret .= ' class="' . $link['class'] . '"'; 1644bb4866bdSchris if (!empty($link['target'])) $ret .= ' target="' . $link['target'] . '"'; 1645bb4866bdSchris if (!empty($link['title'])) $ret .= ' title="' . $link['title'] . '"'; 1646bb4866bdSchris if (!empty($link['style'])) $ret .= ' style="' . $link['style'] . '"'; 1647914045f3SAndreas Gohr if (!empty($link['rel'])) $ret .= ' rel="' . trim($link['rel']) . '"'; 1648bb4866bdSchris if (!empty($link['more'])) $ret .= ' ' . $link['more']; 1649ba11bd29Sandi $ret .= '>'; 1650ba11bd29Sandi $ret .= $link['name']; 1651ba11bd29Sandi $ret .= '</a>'; 1652ba11bd29Sandi $ret .= $link['suf']; 1653ba11bd29Sandi return $ret; 1654ba11bd29Sandi } 1655ba11bd29Sandi 1656ba11bd29Sandi /** 16573fd0b676Sandi * Renders internal and external media 16583fd0b676Sandi * 16593dd5c225SAndreas Gohr * @param string $src media ID 16603dd5c225SAndreas Gohr * @param string $title descriptive text 16613dd5c225SAndreas Gohr * @param string $align left|center|right 16623dd5c225SAndreas Gohr * @param int $width width of media in pixel 16633dd5c225SAndreas Gohr * @param int $height height of media in pixel 16643dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 16653dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 16663dd5c225SAndreas Gohr * @return string 1667faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 16683fd0b676Sandi */ 166995078f23SAndreas Gohr public function _media( 167095078f23SAndreas Gohr $src, 167195078f23SAndreas Gohr $title = null, 167295078f23SAndreas Gohr $align = null, 167395078f23SAndreas Gohr $width = null, 167495078f23SAndreas Gohr $height = null, 167595078f23SAndreas Gohr $cache = null, 167695078f23SAndreas Gohr $render = true 167795078f23SAndreas Gohr ) { 16783fd0b676Sandi 16793fd0b676Sandi $ret = ''; 16803fd0b676Sandi 1681faf3f01bSAndreas Gohr [$ext, $mime] = mimetype($src); 16826c16a3a9Sfiwswe if (str_starts_with($mime, 'image')) { 1683b739ff0fSPierre Spring // first get the $title 1684b739ff0fSPierre Spring if (!is_null($title)) { 1685b739ff0fSPierre Spring $title = $this->_xmlEntities($title); 1686b739ff0fSPierre Spring } elseif ($ext == 'jpg' || $ext == 'jpeg') { 1687b739ff0fSPierre Spring //try to use the caption from IPTC/EXIF 1688b739ff0fSPierre Spring require_once(DOKU_INC . 'inc/JpegMeta.php'); 168967f9913dSAndreas Gohr $jpeg = new JpegMeta(mediaFN($src)); 1690faf3f01bSAndreas Gohr $cap = $jpeg->getTitle(); 16913dd5c225SAndreas Gohr if (!empty($cap)) { 1692b739ff0fSPierre Spring $title = $this->_xmlEntities($cap); 1693b739ff0fSPierre Spring } 1694b739ff0fSPierre Spring } 1695b739ff0fSPierre Spring if (!$render) { 1696b739ff0fSPierre Spring // if the picture is not supposed to be rendered 1697b739ff0fSPierre Spring // return the title of the picture 1698768be5a3SPhy if ($title === null || $title === "") { 1699b739ff0fSPierre Spring // just show the sourcename 1700faf3f01bSAndreas Gohr $title = $this->_xmlEntities(PhpString::basename(noNS($src))); 1701b739ff0fSPierre Spring } 1702b739ff0fSPierre Spring return $title; 1703b739ff0fSPierre Spring } 17043fd0b676Sandi //add image tag 170564159a61SAndreas Gohr $ret .= '<img src="' . ml( 170664159a61SAndreas Gohr $src, 1707faf3f01bSAndreas Gohr [ 1708faf3f01bSAndreas Gohr 'w' => $width, 1709faf3f01bSAndreas Gohr 'h' => $height, 171064159a61SAndreas Gohr 'cache' => $cache, 171164159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 1712faf3f01bSAndreas Gohr ] 171364159a61SAndreas Gohr ) . '"'; 17143fd0b676Sandi $ret .= ' class="media' . $align . '"'; 17154732b197SAndreas Gohr $ret .= ' loading="lazy"'; 17163fd0b676Sandi 1717b739ff0fSPierre Spring if ($title) { 1718b739ff0fSPierre Spring $ret .= ' title="' . $title . '"'; 1719b739ff0fSPierre Spring $ret .= ' alt="' . $title . '"'; 17203fd0b676Sandi } else { 17213fd0b676Sandi $ret .= ' alt=""'; 17223fd0b676Sandi } 17233fd0b676Sandi 1724749bc7f1SAndreas Gohr if (!is_null($width)) { 17253fd0b676Sandi $ret .= ' width="' . $this->_xmlEntities($width) . '"'; 1726749bc7f1SAndreas Gohr } 17273fd0b676Sandi 1728749bc7f1SAndreas Gohr if (!is_null($height)) { 17293fd0b676Sandi $ret .= ' height="' . $this->_xmlEntities($height) . '"'; 1730749bc7f1SAndreas Gohr } 17313fd0b676Sandi 17323fd0b676Sandi $ret .= ' />'; 173317954bb5SAnika Henke } elseif (media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 17342a2a2ba2SAnika Henke // first get the $title 1735749bc7f1SAndreas Gohr $title ??= false; 17362a2a2ba2SAnika Henke if (!$render) { 173717954bb5SAnika Henke // if the file is not supposed to be rendered 173817954bb5SAnika Henke // return the title of the file (just the sourcename if there is no title) 1739faf3f01bSAndreas Gohr return $this->_xmlEntities($title ?: PhpString::basename(noNS($src))); 17402a2a2ba2SAnika Henke } 17412a2a2ba2SAnika Henke 1742faf3f01bSAndreas Gohr $att = []; 17432a2a2ba2SAnika Henke $att['class'] = "media$align"; 174417954bb5SAnika Henke if ($title) { 174517954bb5SAnika Henke $att['title'] = $title; 174617954bb5SAnika Henke } 17472a2a2ba2SAnika Henke 174817954bb5SAnika Henke if (media_supportedav($mime, 'video')) { 174917954bb5SAnika Henke //add video 175079e53fe5SAnika Henke $ret .= $this->_video($src, $width, $height, $att); 1751b44a5dceSAnika Henke } 175217954bb5SAnika Henke if (media_supportedav($mime, 'audio')) { 1753b44a5dceSAnika Henke //add audio 1754b44a5dceSAnika Henke $ret .= $this->_audio($src, $att); 175517954bb5SAnika Henke } 17563fd0b676Sandi } elseif ($mime == 'application/x-shockwave-flash') { 17571c882ba8SAndreas Gohr if (!$render) { 17581c882ba8SAndreas Gohr // if the flash is not supposed to be rendered 17591c882ba8SAndreas Gohr // return the title of the flash 17601c882ba8SAndreas Gohr if (!$title) { 17611c882ba8SAndreas Gohr // just show the sourcename 1762faf3f01bSAndreas Gohr $title = PhpString::basename(noNS($src)); 17631c882ba8SAndreas Gohr } 176407bf32b2SAndreas Gohr return $this->_xmlEntities($title); 17651c882ba8SAndreas Gohr } 17661c882ba8SAndreas Gohr 1767faf3f01bSAndreas Gohr $att = []; 176807bf32b2SAndreas Gohr $att['class'] = "media$align"; 176907bf32b2SAndreas Gohr if ($align == 'right') $att['align'] = 'right'; 177007bf32b2SAndreas Gohr if ($align == 'left') $att['align'] = 'left'; 17713dd5c225SAndreas Gohr $ret .= html_flashobject( 177295078f23SAndreas Gohr ml($src, ['cache' => $cache], true, '&'), 177395078f23SAndreas Gohr $width, 177495078f23SAndreas Gohr $height, 1775faf3f01bSAndreas Gohr ['quality' => 'high'], 177607bf32b2SAndreas Gohr null, 177707bf32b2SAndreas Gohr $att, 17783dd5c225SAndreas Gohr $this->_xmlEntities($title) 17793dd5c225SAndreas Gohr ); 17800f428d7dSAndreas Gohr } elseif ($title) { 17813fd0b676Sandi // well at least we have a title to display 17823fd0b676Sandi $ret .= $this->_xmlEntities($title); 17833fd0b676Sandi } else { 17845291ca3aSAndreas Gohr // just show the sourcename 1785faf3f01bSAndreas Gohr $ret .= $this->_xmlEntities(PhpString::basename(noNS($src))); 17863fd0b676Sandi } 17873fd0b676Sandi 17883fd0b676Sandi return $ret; 17893fd0b676Sandi } 17903fd0b676Sandi 17913dd5c225SAndreas Gohr /** 17923dd5c225SAndreas Gohr * Escape string for output 17933dd5c225SAndreas Gohr * 17943dd5c225SAndreas Gohr * @param $string 17953dd5c225SAndreas Gohr * @return string 17963dd5c225SAndreas Gohr */ 1797faf3f01bSAndreas Gohr public function _xmlEntities($string) 1798faf3f01bSAndreas Gohr { 1799f7711f2bSAndreas Gohr return hsc($string); 18000cecf9d5Sandi } 18010cecf9d5Sandi 1802de369923SAndreas Gohr 1803af587fa8Sandi /** 18043fd0b676Sandi * Construct a title and handle images in titles 18053fd0b676Sandi * 18063dd5c225SAndreas Gohr * @param string|array $title either string title or media array 18073dd5c225SAndreas Gohr * @param string $default default title if nothing else is found 18083dd5c225SAndreas Gohr * @param bool $isImage will be set to true if it's a media file 18093dd5c225SAndreas Gohr * @param null|string $id linked page id (used to extract title from first heading) 18103dd5c225SAndreas Gohr * @param string $linktype content|navigation 18113dd5c225SAndreas Gohr * @return string HTML of the title, might be full image tag or just escaped text 1812faf3f01bSAndreas Gohr * @author Harry Fuecks <hfuecks@gmail.com> 18133fd0b676Sandi */ 1814faf3f01bSAndreas Gohr public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') 1815faf3f01bSAndreas Gohr { 181644881bd0Shenning.noren $isImage = false; 181729657f9eSAndreas Gohr if (is_array($title)) { 181829657f9eSAndreas Gohr $isImage = true; 181929657f9eSAndreas Gohr return $this->_imageTitle($title); 182029657f9eSAndreas Gohr } elseif (is_null($title) || trim($title) == '') { 1821fe9ec250SChris Smith if (useHeading($linktype) && $id) { 182267c15eceSMichael Hamann $heading = p_get_first_heading($id); 1823f515db7fSAndreas Gohr if (!blank($heading)) { 1824433bef32Sandi return $this->_xmlEntities($heading); 1825bb0a59d4Sjan } 1826bb0a59d4Sjan } 1827433bef32Sandi return $this->_xmlEntities($default); 182868c26e6dSMichael Klier } else { 182968c26e6dSMichael Klier return $this->_xmlEntities($title); 18300cecf9d5Sandi } 18310cecf9d5Sandi } 18320cecf9d5Sandi 18330cecf9d5Sandi /** 18343dd5c225SAndreas Gohr * Returns HTML code for images used in link titles 18353fd0b676Sandi * 1836e0c26282SGerrit Uitslag * @param array $img 18373dd5c225SAndreas Gohr * @return string HTML img tag or similar 1838faf3f01bSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 18390cecf9d5Sandi */ 1840faf3f01bSAndreas Gohr public function _imageTitle($img) 1841faf3f01bSAndreas Gohr { 1842d9baf1a7SKazutaka Miyasaka global $ID; 1843d9baf1a7SKazutaka Miyasaka 1844d9baf1a7SKazutaka Miyasaka // some fixes on $img['src'] 1845d9baf1a7SKazutaka Miyasaka // see internalmedia() and externalmedia() 1846faf3f01bSAndreas Gohr [$img['src']] = explode('#', $img['src'], 2); 1847d9baf1a7SKazutaka Miyasaka if ($img['type'] == 'internalmedia') { 18488c6be208SAndreas Gohr $img['src'] = (new MediaResolver($ID))->resolveId($img['src'], $this->date_at, true); 1849d9baf1a7SKazutaka Miyasaka } 1850d9baf1a7SKazutaka Miyasaka 18513dd5c225SAndreas Gohr return $this->_media( 18523dd5c225SAndreas Gohr $img['src'], 18534826ab45Sandi $img['title'], 18544826ab45Sandi $img['align'], 18554826ab45Sandi $img['width'], 18564826ab45Sandi $img['height'], 18573dd5c225SAndreas Gohr $img['cache'] 18583dd5c225SAndreas Gohr ); 18590cecf9d5Sandi } 1860b739ff0fSPierre Spring 1861b739ff0fSPierre Spring /** 18623dd5c225SAndreas Gohr * helperfunction to return a basic link to a media 18633dd5c225SAndreas Gohr * 18643dd5c225SAndreas Gohr * used in internalmedia() and externalmedia() 1865b739ff0fSPierre Spring * 18663dd5c225SAndreas Gohr * @param string $src media ID 18673dd5c225SAndreas Gohr * @param string $title descriptive text 18683dd5c225SAndreas Gohr * @param string $align left|center|right 18693dd5c225SAndreas Gohr * @param int $width width of media in pixel 18703dd5c225SAndreas Gohr * @param int $height height of media in pixel 18713dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 18723dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 18733dd5c225SAndreas Gohr * @return array associative array with link config 1874faf3f01bSAndreas Gohr * @author Pierre Spring <pierre.spring@liip.ch> 1875b739ff0fSPierre Spring */ 1876faf3f01bSAndreas Gohr public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) 1877faf3f01bSAndreas Gohr { 1878b739ff0fSPierre Spring global $conf; 1879b739ff0fSPierre Spring 1880faf3f01bSAndreas Gohr $link = []; 1881b739ff0fSPierre Spring $link['class'] = 'media'; 1882b739ff0fSPierre Spring $link['style'] = ''; 1883b739ff0fSPierre Spring $link['pre'] = ''; 1884b739ff0fSPierre Spring $link['suf'] = ''; 1885b739ff0fSPierre Spring $link['more'] = ''; 1886b739ff0fSPierre Spring $link['target'] = $conf['target']['media']; 1887bc3d2252SAndreas Gohr if ($conf['target']['media']) $link['rel'] = 'noopener'; 1888b739ff0fSPierre Spring $link['title'] = $this->_xmlEntities($src); 1889b739ff0fSPierre Spring $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1890b739ff0fSPierre Spring 1891b739ff0fSPierre Spring return $link; 1892b739ff0fSPierre Spring } 189391459163SAnika Henke 18942a2a2ba2SAnika Henke /** 18952a2a2ba2SAnika Henke * Embed video(s) in HTML 18962a2a2ba2SAnika Henke * 18972a2a2ba2SAnika Henke * @param string $src - ID of video to embed 18982a2a2ba2SAnika Henke * @param int $width - width of the video in pixels 18992a2a2ba2SAnika Henke * @param int $height - height of the video in pixels 19002a2a2ba2SAnika Henke * @param array $atts - additional attributes for the <video> tag 1901f50634f0SAnika Henke * @return string 1902faf3f01bSAndreas Gohr * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 1903faf3f01bSAndreas Gohr * 1904faf3f01bSAndreas Gohr * @author Anika Henke <anika@selfthinker.org> 19052a2a2ba2SAnika Henke */ 1906faf3f01bSAndreas Gohr public function _video($src, $width, $height, $atts = null) 1907faf3f01bSAndreas Gohr { 19082a2a2ba2SAnika Henke // prepare width and height 1909faf3f01bSAndreas Gohr if (is_null($atts)) $atts = []; 19102a2a2ba2SAnika Henke $atts['width'] = (int)$width; 19112a2a2ba2SAnika Henke $atts['height'] = (int)$height; 19122a2a2ba2SAnika Henke if (!$atts['width']) $atts['width'] = 320; 19132a2a2ba2SAnika Henke if (!$atts['height']) $atts['height'] = 240; 19142a2a2ba2SAnika Henke 1915410ee62aSAnika Henke $posterUrl = ''; 1916faf3f01bSAndreas Gohr $files = []; 1917faf3f01bSAndreas Gohr $tracks = []; 1918410ee62aSAnika Henke $isExternal = media_isexternal($src); 1919410ee62aSAnika Henke 1920410ee62aSAnika Henke if ($isExternal) { 1921410ee62aSAnika Henke // take direct source for external files 1922a19c9aa0SGerrit Uitslag [/* ext */, $srcMime] = mimetype($src); 1923410ee62aSAnika Henke $files[$srcMime] = $src; 1924410ee62aSAnika Henke } else { 19253d7a9e0aSAnika Henke // prepare alternative formats 1926faf3f01bSAndreas Gohr $extensions = ['webm', 'ogv', 'mp4']; 1927410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 1928faf3f01bSAndreas Gohr $poster = media_alternativefiles($src, ['jpg', 'png']); 19290877a1f1SSchplurtz le Déboulonné $tracks = media_trackfiles($src); 193099f943f6SAnika Henke if (!empty($poster)) { 19312d338eabSAndreas Gohr $posterUrl = ml(reset($poster), '', true, '&'); 193299f943f6SAnika Henke } 1933410ee62aSAnika Henke } 19342a2a2ba2SAnika Henke 1935f50634f0SAnika Henke $out = ''; 193679e53fe5SAnika Henke // open video tag 1937f50634f0SAnika Henke $out .= '<video ' . buildAttributes($atts) . ' controls="controls"'; 19383641199aSAnika Henke if ($posterUrl) $out .= ' poster="' . hsc($posterUrl) . '"'; 1939f50634f0SAnika Henke $out .= '>' . NL; 19403641199aSAnika Henke $fallback = ''; 194179e53fe5SAnika Henke 194279e53fe5SAnika Henke // output source for each alternative video format 1943410ee62aSAnika Henke foreach ($files as $mime => $file) { 1944410ee62aSAnika Henke if ($isExternal) { 1945410ee62aSAnika Henke $url = $file; 1946410ee62aSAnika Henke $linkType = 'externalmedia'; 1947410ee62aSAnika Henke } else { 19482d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1949410ee62aSAnika Henke $linkType = 'internalmedia'; 1950410ee62aSAnika Henke } 1951faf3f01bSAndreas Gohr $title = empty($atts['title']) 1952faf3f01bSAndreas Gohr ? $this->_xmlEntities(PhpString::basename(noNS($file))) 1953faf3f01bSAndreas Gohr : $atts['title']; 19543d7a9e0aSAnika Henke 1955f50634f0SAnika Henke $out .= '<source src="' . hsc($url) . '" type="' . $mime . '" />' . NL; 195679e53fe5SAnika Henke // alternative content (just a link to the file) 195764159a61SAndreas Gohr $fallback .= $this->$linkType( 195864159a61SAndreas Gohr $file, 195964159a61SAndreas Gohr $title, 196064159a61SAndreas Gohr null, 196164159a61SAndreas Gohr null, 196264159a61SAndreas Gohr null, 196364159a61SAndreas Gohr $cache = null, 196464159a61SAndreas Gohr $linking = 'linkonly', 196564159a61SAndreas Gohr $return = true 196664159a61SAndreas Gohr ); 19673d7a9e0aSAnika Henke } 19682a2a2ba2SAnika Henke 19690877a1f1SSchplurtz le Déboulonné // output each track if any 19700877a1f1SSchplurtz le Déboulonné foreach ($tracks as $trackid => $info) { 1971093fe67eSAndreas Gohr [$kind, $srclang] = array_map(hsc(...), $info); 197223c61bbeSSchplurtz le Déboulonné $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; 197323c61bbeSSchplurtz le Déboulonné $out .= "label=\"$srclang\" "; 19740877a1f1SSchplurtz le Déboulonné $out .= 'src="' . ml($trackid, '', true) . '">' . NL; 19750877a1f1SSchplurtz le Déboulonné } 19760877a1f1SSchplurtz le Déboulonné 19772a2a2ba2SAnika Henke // finish 19783641199aSAnika Henke $out .= $fallback; 1979f50634f0SAnika Henke $out .= '</video>' . NL; 1980f50634f0SAnika Henke return $out; 19812a2a2ba2SAnika Henke } 19822a2a2ba2SAnika Henke 1983b44a5dceSAnika Henke /** 1984b44a5dceSAnika Henke * Embed audio in HTML 1985b44a5dceSAnika Henke * 1986b44a5dceSAnika Henke * @param string $src - ID of audio to embed 19876d4af72aSAnika Henke * @param array $atts - additional attributes for the <audio> tag 1988f50634f0SAnika Henke * @return string 1989faf3f01bSAndreas Gohr * @author Anika Henke <anika@selfthinker.org> 1990faf3f01bSAndreas Gohr * 1991b44a5dceSAnika Henke */ 1992faf3f01bSAndreas Gohr public function _audio($src, $atts = []) 1993faf3f01bSAndreas Gohr { 1994faf3f01bSAndreas Gohr $files = []; 1995410ee62aSAnika Henke $isExternal = media_isexternal($src); 1996b44a5dceSAnika Henke 1997410ee62aSAnika Henke if ($isExternal) { 1998410ee62aSAnika Henke // take direct source for external files 1999a19c9aa0SGerrit Uitslag [/* ext */, $srcMime] = mimetype($src); 2000410ee62aSAnika Henke $files[$srcMime] = $src; 2001410ee62aSAnika Henke } else { 2002b44a5dceSAnika Henke // prepare alternative formats 2003faf3f01bSAndreas Gohr $extensions = ['ogg', 'mp3', 'wav']; 2004410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 2005410ee62aSAnika Henke } 2006b44a5dceSAnika Henke 2007f50634f0SAnika Henke $out = ''; 2008b44a5dceSAnika Henke // open audio tag 2009f50634f0SAnika Henke $out .= '<audio ' . buildAttributes($atts) . ' controls="controls">' . NL; 20103641199aSAnika Henke $fallback = ''; 2011b44a5dceSAnika Henke 2012b44a5dceSAnika Henke // output source for each alternative audio format 2013410ee62aSAnika Henke foreach ($files as $mime => $file) { 2014410ee62aSAnika Henke if ($isExternal) { 2015410ee62aSAnika Henke $url = $file; 2016410ee62aSAnika Henke $linkType = 'externalmedia'; 2017410ee62aSAnika Henke } else { 20182d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 2019410ee62aSAnika Henke $linkType = 'internalmedia'; 2020410ee62aSAnika Henke } 2021faf3f01bSAndreas Gohr $title = $atts['title'] ?: $this->_xmlEntities(PhpString::basename(noNS($file))); 2022b44a5dceSAnika Henke 2023f50634f0SAnika Henke $out .= '<source src="' . hsc($url) . '" type="' . $mime . '" />' . NL; 2024b44a5dceSAnika Henke // alternative content (just a link to the file) 202564159a61SAndreas Gohr $fallback .= $this->$linkType( 202664159a61SAndreas Gohr $file, 202764159a61SAndreas Gohr $title, 202864159a61SAndreas Gohr null, 202964159a61SAndreas Gohr null, 203064159a61SAndreas Gohr null, 203164159a61SAndreas Gohr $cache = null, 203264159a61SAndreas Gohr $linking = 'linkonly', 203364159a61SAndreas Gohr $return = true 203464159a61SAndreas Gohr ); 2035b44a5dceSAnika Henke } 2036b44a5dceSAnika Henke 2037b44a5dceSAnika Henke // finish 20383641199aSAnika Henke $out .= $fallback; 2039f50634f0SAnika Henke $out .= '</audio>' . NL; 2040f50634f0SAnika Henke return $out; 2041b44a5dceSAnika Henke } 2042b44a5dceSAnika Henke 20435c2eed9aSlisps /** 204452dc5eadSlisps * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 20455c2eed9aSlisps * which returns an existing media revision less or equal to rev or date_at 20465c2eed9aSlisps * 20475c2eed9aSlisps * @param string $media_id 20485c2eed9aSlisps * @access protected 20495c2eed9aSlisps * @return string revision ('' for current) 2050faf3f01bSAndreas Gohr * @author lisps 20515c2eed9aSlisps */ 2052faf3f01bSAndreas Gohr protected function _getLastMediaRevisionAt($media_id) 2053faf3f01bSAndreas Gohr { 205452dc5eadSlisps if (!$this->date_at || media_isexternal($media_id)) return ''; 2055252acce3SSatoshi Sahara $changelog = new MediaChangeLog($media_id); 2056252acce3SSatoshi Sahara return $changelog->getLastRevisionAt($this->date_at); 20575c2eed9aSlisps } 20585c2eed9aSlisps 20593dd5c225SAndreas Gohr #endregion 20600cecf9d5Sandi} 20610cecf9d5Sandi 2062e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 2063