10cecf9d5Sandi<?php 20c3a5702SAndreas Gohr 30c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 42cd6cc0aSAndreas Gohruse dokuwiki\File\MediaResolver; 52cd6cc0aSAndreas Gohruse dokuwiki\File\PageResolver; 60c3a5702SAndreas Gohr 7b625487dSandi/** 8b625487dSandi * Renderer for XHTML output 9b625487dSandi * 10b4f2363aSAndreas Gohr * This is DokuWiki's main renderer used to display page content in the wiki 11b4f2363aSAndreas Gohr * 12b625487dSandi * @author Harry Fuecks <hfuecks@gmail.com> 13b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 143dd5c225SAndreas Gohr * 150cecf9d5Sandi */ 16ac83b9d8Sandiclass Doku_Renderer_xhtml extends Doku_Renderer { 173dd5c225SAndreas Gohr /** @var array store the table of contents */ 183dd5c225SAndreas Gohr public $toc = array(); 190cecf9d5Sandi 203dd5c225SAndreas Gohr /** @var array A stack of section edit data */ 213dd5c225SAndreas Gohr protected $sectionedits = array(); 22de369923SAndreas Gohr 23de369923SAndreas Gohr /** @var string|int link pages and media against this revision */ 24de369923SAndreas Gohr public $date_at = ''; 25c5a8fd96SAndreas Gohr 263dd5c225SAndreas Gohr /** @var int last section edit id, used by startSectionEdit */ 273dd5c225SAndreas Gohr protected $lastsecid = 0; 280cecf9d5Sandi 2916ec3e37SAndreas Gohr /** @var array a list of footnotes, list starts at 1! */ 303dd5c225SAndreas Gohr protected $footnotes = array(); 317764a90aSandi 323dd5c225SAndreas Gohr /** @var int current section level */ 333dd5c225SAndreas Gohr protected $lastlevel = 0; 343dd5c225SAndreas Gohr /** @var array section node tracker */ 353dd5c225SAndreas Gohr protected $node = array(0, 0, 0, 0, 0); 363dd5c225SAndreas Gohr 373dd5c225SAndreas Gohr /** @var string temporary $doc store */ 383dd5c225SAndreas Gohr protected $store = ''; 393dd5c225SAndreas Gohr 403dd5c225SAndreas Gohr /** @var array global counter, for table classes etc. */ 413dd5c225SAndreas Gohr protected $_counter = array(); // 423dd5c225SAndreas Gohr 433dd5c225SAndreas Gohr /** @var int counts the code and file blocks, used to provide download links */ 443dd5c225SAndreas Gohr protected $_codeblock = 0; 453dd5c225SAndreas Gohr 463dd5c225SAndreas Gohr /** @var array list of allowed URL schemes */ 473dd5c225SAndreas Gohr protected $schemes = null; 48b5742cedSPierre Spring 4990df9a4dSAdrian Lang /** 5090df9a4dSAdrian Lang * Register a new edit section range 5190df9a4dSAdrian Lang * 5242ea7f44SGerrit Uitslag * @param int $start The byte position for the edit start 53ec57f119SLarsDW223 * @param array $data Associative array with section data: 54ec57f119SLarsDW223 * Key 'name': the section name/title 55ec57f119SLarsDW223 * Key 'target': the target for the section edit, 56ec57f119SLarsDW223 * e.g. 'section' or 'table' 57ec57f119SLarsDW223 * Key 'hid': header id 58ec57f119SLarsDW223 * Key 'codeblockOffset': actual code block index 59ec57f119SLarsDW223 * Key 'start': set in startSectionEdit(), 60ec57f119SLarsDW223 * do not set yourself 61ec57f119SLarsDW223 * Key 'range': calculated from 'start' and 62ec57f119SLarsDW223 * $key in finishSectionEdit(), 63ec57f119SLarsDW223 * do not set yourself 6490df9a4dSAdrian Lang * @return string A marker class for the starting HTML element 6542ea7f44SGerrit Uitslag * 6690df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 6790df9a4dSAdrian Lang */ 68ec57f119SLarsDW223 public function startSectionEdit($start, $data) { 69ec57f119SLarsDW223 if (!is_array($data)) { 70ac025fdfSAndreas Gohr msg( 71ac025fdfSAndreas Gohr sprintf( 72ac025fdfSAndreas Gohr 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', 73ac025fdfSAndreas Gohr hsc((string) $data) 74ac025fdfSAndreas Gohr ), -1 75ac025fdfSAndreas Gohr ); 76ac025fdfSAndreas Gohr 77ac025fdfSAndreas Gohr // @deprecated 2018-04-14, backward compatibility 78ac025fdfSAndreas Gohr $args = func_get_args(); 79ac025fdfSAndreas Gohr $data = array(); 80ac025fdfSAndreas Gohr if(isset($args[1])) $data['target'] = $args[1]; 81ac025fdfSAndreas Gohr if(isset($args[2])) $data['name'] = $args[2]; 82ac025fdfSAndreas Gohr if(isset($args[3])) $data['hid'] = $args[3]; 83ec57f119SLarsDW223 } 84ec57f119SLarsDW223 $data['secid'] = ++$this->lastsecid; 85ec57f119SLarsDW223 $data['start'] = $start; 86ec57f119SLarsDW223 $this->sectionedits[] = $data; 87ec57f119SLarsDW223 return 'sectionedit'.$data['secid']; 8890df9a4dSAdrian Lang } 8990df9a4dSAdrian Lang 9090df9a4dSAdrian Lang /** 9190df9a4dSAdrian Lang * Finish an edit section range 9290df9a4dSAdrian Lang * 9342ea7f44SGerrit Uitslag * @param int $end The byte position for the edit end; null for the rest of the page 9442ea7f44SGerrit Uitslag * 9590df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 9690df9a4dSAdrian Lang */ 972571786cSLarsDW223 public function finishSectionEdit($end = null, $hid = null) { 98ec57f119SLarsDW223 $data = array_pop($this->sectionedits); 99*0d9f02ecSasivery if(is_null($data)) { 100*0d9f02ecSasivery return; 101*0d9f02ecSasivery } 102*0d9f02ecSasivery 103ec57f119SLarsDW223 if(!is_null($end) && $end <= $data['start']) { 10400c13053SAdrian Lang return; 10500c13053SAdrian Lang } 1062571786cSLarsDW223 if(!is_null($hid)) { 107ec57f119SLarsDW223 $data['hid'] .= $hid; 1082571786cSLarsDW223 } 109ec57f119SLarsDW223 $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end); 110ec57f119SLarsDW223 unset($data['start']); 111ada0d779SMichael Hamann $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->'; 11290df9a4dSAdrian Lang } 11390df9a4dSAdrian Lang 1143dd5c225SAndreas Gohr /** 1153dd5c225SAndreas Gohr * Returns the format produced by this renderer. 1163dd5c225SAndreas Gohr * 1173dd5c225SAndreas Gohr * @return string always 'xhtml' 1183dd5c225SAndreas Gohr */ 119de369923SAndreas Gohr public function getFormat() { 1205f70445dSAndreas Gohr return 'xhtml'; 1215f70445dSAndreas Gohr } 1225f70445dSAndreas Gohr 1233dd5c225SAndreas Gohr /** 1243dd5c225SAndreas Gohr * Initialize the document 1253dd5c225SAndreas Gohr */ 126de369923SAndreas Gohr public function document_start() { 127c5a8fd96SAndreas Gohr //reset some internals 128c5a8fd96SAndreas Gohr $this->toc = array(); 1290cecf9d5Sandi } 1300cecf9d5Sandi 1313dd5c225SAndreas Gohr /** 1323dd5c225SAndreas Gohr * Finalize the document 1333dd5c225SAndreas Gohr */ 134de369923SAndreas Gohr public function document_end() { 13590df9a4dSAdrian Lang // Finish open section edits. 13690df9a4dSAdrian Lang while(count($this->sectionedits) > 0) { 137ec57f119SLarsDW223 if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { 13890df9a4dSAdrian Lang // If there is only one section, do not write a section edit 13990df9a4dSAdrian Lang // marker. 14090df9a4dSAdrian Lang array_pop($this->sectionedits); 14190df9a4dSAdrian Lang } else { 142d9e36cbeSAdrian Lang $this->finishSectionEdit(); 14390df9a4dSAdrian Lang } 14490df9a4dSAdrian Lang } 14590df9a4dSAdrian Lang 1460cecf9d5Sandi if(count($this->footnotes) > 0) { 147a2d649c4Sandi $this->doc .= '<div class="footnotes">'.DOKU_LF; 148d74aace9Schris 14916ec3e37SAndreas Gohr foreach($this->footnotes as $id => $footnote) { 150d74aace9Schris // check its not a placeholder that indicates actual footnote text is elsewhere 151d74aace9Schris if(substr($footnote, 0, 5) != "@@FNT") { 152d74aace9Schris 153d74aace9Schris // open the footnote and set the anchor and backlink 154d74aace9Schris $this->doc .= '<div class="fn">'; 15516cc7ed7SAnika Henke $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">'; 15629bfcd16SAndreas Gohr $this->doc .= $id.')</a></sup> '.DOKU_LF; 157d74aace9Schris 158d74aace9Schris // get any other footnotes that use the same markup 159d74aace9Schris $alt = array_keys($this->footnotes, "@@FNT$id"); 160d74aace9Schris 161d74aace9Schris if(count($alt)) { 162d74aace9Schris foreach($alt as $ref) { 163d74aace9Schris // set anchor and backlink for the other footnotes 16416ec3e37SAndreas Gohr $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">'; 16516ec3e37SAndreas Gohr $this->doc .= ($ref).')</a></sup> '.DOKU_LF; 166d74aace9Schris } 167d74aace9Schris } 168d74aace9Schris 169d74aace9Schris // add footnote markup and close this footnote 170694afa06SAnika Henke $this->doc .= '<div class="content">'.$footnote.'</div>'; 171d74aace9Schris $this->doc .= '</div>'.DOKU_LF; 172d74aace9Schris } 1730cecf9d5Sandi } 174a2d649c4Sandi $this->doc .= '</div>'.DOKU_LF; 1750cecf9d5Sandi } 176c5a8fd96SAndreas Gohr 177b8595a66SAndreas Gohr // Prepare the TOC 178851f2e89SAnika Henke global $conf; 17964159a61SAndreas Gohr if( 18064159a61SAndreas Gohr $this->info['toc'] && 18164159a61SAndreas Gohr is_array($this->toc) && 18264159a61SAndreas Gohr $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] 18364159a61SAndreas Gohr ) { 184b8595a66SAndreas Gohr global $TOC; 185b8595a66SAndreas Gohr $TOC = $this->toc; 1860cecf9d5Sandi } 1873e55d035SAndreas Gohr 1883e55d035SAndreas Gohr // make sure there are no empty paragraphs 18927918226Schris $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 190e41c4da9SAndreas Gohr } 1910cecf9d5Sandi 1923dd5c225SAndreas Gohr /** 1933dd5c225SAndreas Gohr * Add an item to the TOC 1943dd5c225SAndreas Gohr * 1953dd5c225SAndreas Gohr * @param string $id the hash link 1963dd5c225SAndreas Gohr * @param string $text the text to display 1973dd5c225SAndreas Gohr * @param int $level the nesting level 1983dd5c225SAndreas Gohr */ 199de369923SAndreas Gohr public function toc_additem($id, $text, $level) { 200af587fa8Sandi global $conf; 201af587fa8Sandi 202c5a8fd96SAndreas Gohr //handle TOC 203c5a8fd96SAndreas Gohr if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 2047d91652aSAndreas Gohr $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 205c5a8fd96SAndreas Gohr } 206e7856beaSchris } 207e7856beaSchris 2083dd5c225SAndreas Gohr /** 2093dd5c225SAndreas Gohr * Render a heading 2103dd5c225SAndreas Gohr * 2113dd5c225SAndreas Gohr * @param string $text the text to display 2123dd5c225SAndreas Gohr * @param int $level header level 2133dd5c225SAndreas Gohr * @param int $pos byte position in the original source 214e3c00e6eSIain Hallam * @param bool $returnonly whether to return html or write to doc attribute 215e3c00e6eSIain Hallam * @return void|string writes to doc attribute or returns html depends on $returnonly 2163dd5c225SAndreas Gohr */ 217e3c00e6eSIain Hallam public function header($text, $level, $pos, $returnonly = false) { 21890df9a4dSAdrian Lang global $conf; 21990df9a4dSAdrian Lang 220f515db7fSAndreas Gohr if(blank($text)) return; //skip empty headlines 221e7856beaSchris 222e7856beaSchris $hid = $this->_headerToLink($text, true); 223e7856beaSchris 224e7856beaSchris //only add items within configured levels 225e7856beaSchris $this->toc_additem($hid, $text, $level); 226c5a8fd96SAndreas Gohr 22791459163SAnika Henke // adjust $node to reflect hierarchy of levels 22891459163SAnika Henke $this->node[$level - 1]++; 22991459163SAnika Henke if($level < $this->lastlevel) { 23091459163SAnika Henke for($i = 0; $i < $this->lastlevel - $level; $i++) { 23191459163SAnika Henke $this->node[$this->lastlevel - $i - 1] = 0; 23291459163SAnika Henke } 23391459163SAnika Henke } 23491459163SAnika Henke $this->lastlevel = $level; 23591459163SAnika Henke 23690df9a4dSAdrian Lang if($level <= $conf['maxseclevel'] && 23790df9a4dSAdrian Lang count($this->sectionedits) > 0 && 238ec57f119SLarsDW223 $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' 2393dd5c225SAndreas Gohr ) { 2406c1f778cSAdrian Lang $this->finishSectionEdit($pos - 1); 24190df9a4dSAdrian Lang } 24290df9a4dSAdrian Lang 243e3c00e6eSIain Hallam // build the header 244e3c00e6eSIain Hallam $header = DOKU_LF.'<h'.$level; 24590df9a4dSAdrian Lang if($level <= $conf['maxseclevel']) { 246ec57f119SLarsDW223 $data = array(); 247ec57f119SLarsDW223 $data['target'] = 'section'; 248ec57f119SLarsDW223 $data['name'] = $text; 249ec57f119SLarsDW223 $data['hid'] = $hid; 250ec57f119SLarsDW223 $data['codeblockOffset'] = $this->_codeblock; 251e3c00e6eSIain Hallam $header .= ' class="'.$this->startSectionEdit($pos, $data).'"'; 25290df9a4dSAdrian Lang } 253e3c00e6eSIain Hallam $header .= ' id="'.$hid.'">'; 254e3c00e6eSIain Hallam $header .= $this->_xmlEntities($text); 255e3c00e6eSIain Hallam $header .= "</h$level>".DOKU_LF; 256e3c00e6eSIain Hallam 257e3c00e6eSIain Hallam if ($returnonly) { 258e3c00e6eSIain Hallam return $header; 259e3c00e6eSIain Hallam } else { 260e3c00e6eSIain Hallam $this->doc .= $header; 261e3c00e6eSIain Hallam } 2620cecf9d5Sandi } 2630cecf9d5Sandi 2643dd5c225SAndreas Gohr /** 2653dd5c225SAndreas Gohr * Open a new section 2663dd5c225SAndreas Gohr * 2673dd5c225SAndreas Gohr * @param int $level section level (as determined by the previous header) 2683dd5c225SAndreas Gohr */ 269de369923SAndreas Gohr public function section_open($level) { 2709864e7b1SAdrian Lang $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; 2710cecf9d5Sandi } 2720cecf9d5Sandi 2733dd5c225SAndreas Gohr /** 2743dd5c225SAndreas Gohr * Close the current section 2753dd5c225SAndreas Gohr */ 276de369923SAndreas Gohr public function section_close() { 277a2d649c4Sandi $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 2780cecf9d5Sandi } 2790cecf9d5Sandi 2803dd5c225SAndreas Gohr /** 2813dd5c225SAndreas Gohr * Render plain text data 2823dd5c225SAndreas Gohr * 2833dd5c225SAndreas Gohr * @param $text 2843dd5c225SAndreas Gohr */ 285de369923SAndreas Gohr public function cdata($text) { 286a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 2870cecf9d5Sandi } 2880cecf9d5Sandi 2893dd5c225SAndreas Gohr /** 2903dd5c225SAndreas Gohr * Open a paragraph 2913dd5c225SAndreas Gohr */ 292de369923SAndreas Gohr public function p_open() { 29359869a4bSAnika Henke $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 2940cecf9d5Sandi } 2950cecf9d5Sandi 2963dd5c225SAndreas Gohr /** 2973dd5c225SAndreas Gohr * Close a paragraph 2983dd5c225SAndreas Gohr */ 299de369923SAndreas Gohr public function p_close() { 30059869a4bSAnika Henke $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 3010cecf9d5Sandi } 3020cecf9d5Sandi 3033dd5c225SAndreas Gohr /** 3043dd5c225SAndreas Gohr * Create a line break 3053dd5c225SAndreas Gohr */ 306de369923SAndreas Gohr public function linebreak() { 307a2d649c4Sandi $this->doc .= '<br/>'.DOKU_LF; 3080cecf9d5Sandi } 3090cecf9d5Sandi 3103dd5c225SAndreas Gohr /** 3113dd5c225SAndreas Gohr * Create a horizontal line 3123dd5c225SAndreas Gohr */ 313de369923SAndreas Gohr public function hr() { 3144beabca9SAnika Henke $this->doc .= '<hr />'.DOKU_LF; 3150cecf9d5Sandi } 3160cecf9d5Sandi 3173dd5c225SAndreas Gohr /** 3183dd5c225SAndreas Gohr * Start strong (bold) formatting 3193dd5c225SAndreas Gohr */ 320de369923SAndreas Gohr public function strong_open() { 321a2d649c4Sandi $this->doc .= '<strong>'; 3220cecf9d5Sandi } 3230cecf9d5Sandi 3243dd5c225SAndreas Gohr /** 3253dd5c225SAndreas Gohr * Stop strong (bold) formatting 3263dd5c225SAndreas Gohr */ 327de369923SAndreas Gohr public function strong_close() { 328a2d649c4Sandi $this->doc .= '</strong>'; 3290cecf9d5Sandi } 3300cecf9d5Sandi 3313dd5c225SAndreas Gohr /** 3323dd5c225SAndreas Gohr * Start emphasis (italics) formatting 3333dd5c225SAndreas Gohr */ 334de369923SAndreas Gohr public function emphasis_open() { 335a2d649c4Sandi $this->doc .= '<em>'; 3360cecf9d5Sandi } 3370cecf9d5Sandi 3383dd5c225SAndreas Gohr /** 3393dd5c225SAndreas Gohr * Stop emphasis (italics) formatting 3403dd5c225SAndreas Gohr */ 341de369923SAndreas Gohr public function emphasis_close() { 342a2d649c4Sandi $this->doc .= '</em>'; 3430cecf9d5Sandi } 3440cecf9d5Sandi 3453dd5c225SAndreas Gohr /** 3463dd5c225SAndreas Gohr * Start underline formatting 3473dd5c225SAndreas Gohr */ 348de369923SAndreas Gohr public function underline_open() { 34902e51121SAnika Henke $this->doc .= '<em class="u">'; 3500cecf9d5Sandi } 3510cecf9d5Sandi 3523dd5c225SAndreas Gohr /** 3533dd5c225SAndreas Gohr * Stop underline formatting 3543dd5c225SAndreas Gohr */ 355de369923SAndreas Gohr public function underline_close() { 35602e51121SAnika Henke $this->doc .= '</em>'; 3570cecf9d5Sandi } 3580cecf9d5Sandi 3593dd5c225SAndreas Gohr /** 3603dd5c225SAndreas Gohr * Start monospace formatting 3613dd5c225SAndreas Gohr */ 362de369923SAndreas Gohr public function monospace_open() { 363a2d649c4Sandi $this->doc .= '<code>'; 3640cecf9d5Sandi } 3650cecf9d5Sandi 3663dd5c225SAndreas Gohr /** 3673dd5c225SAndreas Gohr * Stop monospace formatting 3683dd5c225SAndreas Gohr */ 369de369923SAndreas Gohr public function monospace_close() { 370a2d649c4Sandi $this->doc .= '</code>'; 3710cecf9d5Sandi } 3720cecf9d5Sandi 3733dd5c225SAndreas Gohr /** 3743dd5c225SAndreas Gohr * Start a subscript 3753dd5c225SAndreas Gohr */ 376de369923SAndreas Gohr public function subscript_open() { 377a2d649c4Sandi $this->doc .= '<sub>'; 3780cecf9d5Sandi } 3790cecf9d5Sandi 3803dd5c225SAndreas Gohr /** 3813dd5c225SAndreas Gohr * Stop a subscript 3823dd5c225SAndreas Gohr */ 383de369923SAndreas Gohr public function subscript_close() { 384a2d649c4Sandi $this->doc .= '</sub>'; 3850cecf9d5Sandi } 3860cecf9d5Sandi 3873dd5c225SAndreas Gohr /** 3883dd5c225SAndreas Gohr * Start a superscript 3893dd5c225SAndreas Gohr */ 390de369923SAndreas Gohr public function superscript_open() { 391a2d649c4Sandi $this->doc .= '<sup>'; 3920cecf9d5Sandi } 3930cecf9d5Sandi 3943dd5c225SAndreas Gohr /** 3953dd5c225SAndreas Gohr * Stop a superscript 3963dd5c225SAndreas Gohr */ 397de369923SAndreas Gohr public function superscript_close() { 398a2d649c4Sandi $this->doc .= '</sup>'; 3990cecf9d5Sandi } 4000cecf9d5Sandi 4013dd5c225SAndreas Gohr /** 4023dd5c225SAndreas Gohr * Start deleted (strike-through) formatting 4033dd5c225SAndreas Gohr */ 404de369923SAndreas Gohr public function deleted_open() { 405a2d649c4Sandi $this->doc .= '<del>'; 4060cecf9d5Sandi } 4070cecf9d5Sandi 4083dd5c225SAndreas Gohr /** 4093dd5c225SAndreas Gohr * Stop deleted (strike-through) formatting 4103dd5c225SAndreas Gohr */ 411de369923SAndreas Gohr public function deleted_close() { 412a2d649c4Sandi $this->doc .= '</del>'; 4130cecf9d5Sandi } 4140cecf9d5Sandi 4153fd0b676Sandi /** 4163fd0b676Sandi * Callback for footnote start syntax 4173fd0b676Sandi * 4183fd0b676Sandi * All following content will go to the footnote instead of 419d74aace9Schris * the document. To achieve this the previous rendered content 4203fd0b676Sandi * is moved to $store and $doc is cleared 4213fd0b676Sandi * 4223fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 4233fd0b676Sandi */ 424de369923SAndreas Gohr public function footnote_open() { 4257764a90aSandi 4267764a90aSandi // move current content to store and record footnote 4277764a90aSandi $this->store = $this->doc; 4287764a90aSandi $this->doc = ''; 4290cecf9d5Sandi } 4300cecf9d5Sandi 4313fd0b676Sandi /** 4323fd0b676Sandi * Callback for footnote end syntax 4333fd0b676Sandi * 4343fd0b676Sandi * All rendered content is moved to the $footnotes array and the old 4353fd0b676Sandi * content is restored from $store again 4363fd0b676Sandi * 4373fd0b676Sandi * @author Andreas Gohr 4383fd0b676Sandi */ 439de369923SAndreas Gohr public function footnote_close() { 44016ec3e37SAndreas Gohr /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 44116ec3e37SAndreas Gohr static $fnid = 0; 44216ec3e37SAndreas Gohr // assign new footnote id (we start at 1) 44316ec3e37SAndreas Gohr $fnid++; 4447764a90aSandi 445d74aace9Schris // recover footnote into the stack and restore old content 446d74aace9Schris $footnote = $this->doc; 4477764a90aSandi $this->doc = $this->store; 4487764a90aSandi $this->store = ''; 449d74aace9Schris 450d74aace9Schris // check to see if this footnote has been seen before 451d74aace9Schris $i = array_search($footnote, $this->footnotes); 452d74aace9Schris 453d74aace9Schris if($i === false) { 454d74aace9Schris // its a new footnote, add it to the $footnotes array 45516ec3e37SAndreas Gohr $this->footnotes[$fnid] = $footnote; 456d74aace9Schris } else { 45716ec3e37SAndreas Gohr // seen this one before, save a placeholder 45816ec3e37SAndreas Gohr $this->footnotes[$fnid] = "@@FNT".($i); 459d74aace9Schris } 460d74aace9Schris 4616b379cbfSAndreas Gohr // output the footnote reference and link 46216ec3e37SAndreas Gohr $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>'; 4630cecf9d5Sandi } 4640cecf9d5Sandi 4653dd5c225SAndreas Gohr /** 4663dd5c225SAndreas Gohr * Open an unordered list 4670c4c0281SGerrit Uitslag * 4687d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4693dd5c225SAndreas Gohr */ 470de369923SAndreas Gohr public function listu_open($classes = null) { 4710c4c0281SGerrit Uitslag $class = ''; 4720c4c0281SGerrit Uitslag if($classes !== null) { 4732e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4740c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4750c4c0281SGerrit Uitslag } 4760c4c0281SGerrit Uitslag $this->doc .= "<ul$class>".DOKU_LF; 4770cecf9d5Sandi } 4780cecf9d5Sandi 4793dd5c225SAndreas Gohr /** 4803dd5c225SAndreas Gohr * Close an unordered list 4813dd5c225SAndreas Gohr */ 482de369923SAndreas Gohr public function listu_close() { 483a2d649c4Sandi $this->doc .= '</ul>'.DOKU_LF; 4840cecf9d5Sandi } 4850cecf9d5Sandi 4863dd5c225SAndreas Gohr /** 4873dd5c225SAndreas Gohr * Open an ordered list 4880c4c0281SGerrit Uitslag * 4897d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4903dd5c225SAndreas Gohr */ 491de369923SAndreas Gohr public function listo_open($classes = null) { 4920c4c0281SGerrit Uitslag $class = ''; 4930c4c0281SGerrit Uitslag if($classes !== null) { 4942e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4950c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4960c4c0281SGerrit Uitslag } 4970c4c0281SGerrit Uitslag $this->doc .= "<ol$class>".DOKU_LF; 4980cecf9d5Sandi } 4990cecf9d5Sandi 5003dd5c225SAndreas Gohr /** 5013dd5c225SAndreas Gohr * Close an ordered list 5023dd5c225SAndreas Gohr */ 503de369923SAndreas Gohr public function listo_close() { 504a2d649c4Sandi $this->doc .= '</ol>'.DOKU_LF; 5050cecf9d5Sandi } 5060cecf9d5Sandi 5073dd5c225SAndreas Gohr /** 5083dd5c225SAndreas Gohr * Open a list item 5093dd5c225SAndreas Gohr * 5103dd5c225SAndreas Gohr * @param int $level the nesting level 511e3a24861SChristopher Smith * @param bool $node true when a node; false when a leaf 5123dd5c225SAndreas Gohr */ 513de369923SAndreas Gohr public function listitem_open($level, $node=false) { 514e3a24861SChristopher Smith $branching = $node ? ' node' : ''; 515e3a24861SChristopher Smith $this->doc .= '<li class="level'.$level.$branching.'">'; 5160cecf9d5Sandi } 5170cecf9d5Sandi 5183dd5c225SAndreas Gohr /** 5193dd5c225SAndreas Gohr * Close a list item 5203dd5c225SAndreas Gohr */ 521de369923SAndreas Gohr public function listitem_close() { 522a2d649c4Sandi $this->doc .= '</li>'.DOKU_LF; 5230cecf9d5Sandi } 5240cecf9d5Sandi 5253dd5c225SAndreas Gohr /** 5263dd5c225SAndreas Gohr * Start the content of a list item 5273dd5c225SAndreas Gohr */ 528de369923SAndreas Gohr public function listcontent_open() { 52990db23d7Schris $this->doc .= '<div class="li">'; 5300cecf9d5Sandi } 5310cecf9d5Sandi 5323dd5c225SAndreas Gohr /** 5333dd5c225SAndreas Gohr * Stop the content of a list item 5343dd5c225SAndreas Gohr */ 535de369923SAndreas Gohr public function listcontent_close() { 53659869a4bSAnika Henke $this->doc .= '</div>'.DOKU_LF; 5370cecf9d5Sandi } 5380cecf9d5Sandi 5393dd5c225SAndreas Gohr /** 5403dd5c225SAndreas Gohr * Output unformatted $text 5413dd5c225SAndreas Gohr * 5423dd5c225SAndreas Gohr * Defaults to $this->cdata() 5433dd5c225SAndreas Gohr * 5443dd5c225SAndreas Gohr * @param string $text 5453dd5c225SAndreas Gohr */ 546de369923SAndreas Gohr public function unformatted($text) { 547a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 5480cecf9d5Sandi } 5490cecf9d5Sandi 5500cecf9d5Sandi /** 5513dd5c225SAndreas Gohr * Start a block quote 5523dd5c225SAndreas Gohr */ 553de369923SAndreas Gohr public function quote_open() { 55496331712SAnika Henke $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 5550cecf9d5Sandi } 5560cecf9d5Sandi 5573dd5c225SAndreas Gohr /** 5583dd5c225SAndreas Gohr * Stop a block quote 5593dd5c225SAndreas Gohr */ 560de369923SAndreas Gohr public function quote_close() { 56196331712SAnika Henke $this->doc .= '</div></blockquote>'.DOKU_LF; 5620cecf9d5Sandi } 5630cecf9d5Sandi 5643dd5c225SAndreas Gohr /** 5653dd5c225SAndreas Gohr * Output preformatted text 5663dd5c225SAndreas Gohr * 5673dd5c225SAndreas Gohr * @param string $text 5683dd5c225SAndreas Gohr */ 569de369923SAndreas Gohr public function preformatted($text) { 570c9250713SAnika Henke $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; 5713d491f75SAndreas Gohr } 5723d491f75SAndreas Gohr 5733dd5c225SAndreas Gohr /** 5743dd5c225SAndreas Gohr * Display text as file content, optionally syntax highlighted 5753dd5c225SAndreas Gohr * 5763dd5c225SAndreas Gohr * @param string $text text to show 5773dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 5783dd5c225SAndreas Gohr * @param string $filename file path label 579e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 5803dd5c225SAndreas Gohr */ 581de369923SAndreas Gohr public function file($text, $language = null, $filename = null, $options=null) { 582e2d88156SLarsDW223 $this->_highlight('file', $text, $language, $filename, $options); 5833d491f75SAndreas Gohr } 5843d491f75SAndreas Gohr 5853dd5c225SAndreas Gohr /** 5863dd5c225SAndreas Gohr * Display text as code content, optionally syntax highlighted 5873dd5c225SAndreas Gohr * 5883dd5c225SAndreas Gohr * @param string $text text to show 5893dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 5903dd5c225SAndreas Gohr * @param string $filename file path label 591e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 5923dd5c225SAndreas Gohr */ 593de369923SAndreas Gohr public function code($text, $language = null, $filename = null, $options=null) { 594e2d88156SLarsDW223 $this->_highlight('code', $text, $language, $filename, $options); 5953d491f75SAndreas Gohr } 5963d491f75SAndreas Gohr 5970cecf9d5Sandi /** 5983d491f75SAndreas Gohr * Use GeSHi to highlight language syntax in code and file blocks 5993fd0b676Sandi * 6003fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 6013dd5c225SAndreas Gohr * @param string $type code|file 6023dd5c225SAndreas Gohr * @param string $text text to show 6033dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6043dd5c225SAndreas Gohr * @param string $filename file path label 605e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6060cecf9d5Sandi */ 607de369923SAndreas Gohr public function _highlight($type, $text, $language = null, $filename = null, $options = null) { 6083d491f75SAndreas Gohr global $ID; 6093d491f75SAndreas Gohr global $lang; 610ec57f119SLarsDW223 global $INPUT; 6113d491f75SAndreas Gohr 612bf8f8509SAndreas Gohr $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language ?? ''); 61356bd9509SPhy 6143d491f75SAndreas Gohr if($filename) { 615190c56e8SAndreas Gohr // add icon 61627bf7924STom N Harris list($ext) = mimetype($filename, false); 617190c56e8SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 618190c56e8SAndreas Gohr $class = 'mediafile mf_'.$class; 619190c56e8SAndreas Gohr 620ec57f119SLarsDW223 $offset = 0; 621ec57f119SLarsDW223 if ($INPUT->has('codeblockOffset')) { 622ec57f119SLarsDW223 $offset = $INPUT->str('codeblockOffset'); 623ec57f119SLarsDW223 } 6243d491f75SAndreas Gohr $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 62564159a61SAndreas Gohr $this->doc .= '<dt><a href="' . 62664159a61SAndreas Gohr exportlink( 62764159a61SAndreas Gohr $ID, 62864159a61SAndreas Gohr 'code', 62964159a61SAndreas Gohr array('codeblock' => $offset + $this->_codeblock) 63064159a61SAndreas Gohr ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; 6313d491f75SAndreas Gohr $this->doc .= hsc($filename); 6323d491f75SAndreas Gohr $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 6333d491f75SAndreas Gohr } 6340cecf9d5Sandi 6352401f18dSSyntaxseed if($text[0] == "\n") { 636d43aac1cSGina Haeussge $text = substr($text, 1); 637d43aac1cSGina Haeussge } 638d43aac1cSGina Haeussge if(substr($text, -1) == "\n") { 639d43aac1cSGina Haeussge $text = substr($text, 0, -1); 640d43aac1cSGina Haeussge } 641d43aac1cSGina Haeussge 642a056e285SPhy if(empty($language)) { // empty is faster than is_null and can prevent '' string 6433d491f75SAndreas Gohr $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF; 6440cecf9d5Sandi } else { 6453d491f75SAndreas Gohr $class = 'code'; //we always need the code class to make the syntax highlighting apply 6463d491f75SAndreas Gohr if($type != 'code') $class .= ' '.$type; 6473d491f75SAndreas Gohr 64864159a61SAndreas Gohr $this->doc .= "<pre class=\"$class $language\">" . 64964159a61SAndreas Gohr p_xhtml_cached_geshi($text, $language, '', $options) . 65064159a61SAndreas Gohr '</pre>' . DOKU_LF; 6510cecf9d5Sandi } 6523d491f75SAndreas Gohr 6533d491f75SAndreas Gohr if($filename) { 6543d491f75SAndreas Gohr $this->doc .= '</dd></dl>'.DOKU_LF; 6553d491f75SAndreas Gohr } 6563d491f75SAndreas Gohr 6573d491f75SAndreas Gohr $this->_codeblock++; 6580cecf9d5Sandi } 6590cecf9d5Sandi 6603dd5c225SAndreas Gohr /** 6613dd5c225SAndreas Gohr * Format an acronym 6623dd5c225SAndreas Gohr * 6633dd5c225SAndreas Gohr * Uses $this->acronyms 6643dd5c225SAndreas Gohr * 6653dd5c225SAndreas Gohr * @param string $acronym 6663dd5c225SAndreas Gohr */ 667de369923SAndreas Gohr public function acronym($acronym) { 6680cecf9d5Sandi 6690cecf9d5Sandi if(array_key_exists($acronym, $this->acronyms)) { 6700cecf9d5Sandi 671433bef32Sandi $title = $this->_xmlEntities($this->acronyms[$acronym]); 6720cecf9d5Sandi 673940db3a3SAnika Henke $this->doc .= '<abbr title="'.$title 674940db3a3SAnika Henke .'">'.$this->_xmlEntities($acronym).'</abbr>'; 6750cecf9d5Sandi 6760cecf9d5Sandi } else { 677a2d649c4Sandi $this->doc .= $this->_xmlEntities($acronym); 6780cecf9d5Sandi } 6790cecf9d5Sandi } 6800cecf9d5Sandi 6813dd5c225SAndreas Gohr /** 6823dd5c225SAndreas Gohr * Format a smiley 6833dd5c225SAndreas Gohr * 6843dd5c225SAndreas Gohr * Uses $this->smiley 6853dd5c225SAndreas Gohr * 6863dd5c225SAndreas Gohr * @param string $smiley 6873dd5c225SAndreas Gohr */ 688de369923SAndreas Gohr public function smiley($smiley) { 689b09504a9SAndreas Gohr if (isset($this->smileys[$smiley])) { 690f62ea8a1Sandi $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] . 691b09504a9SAndreas Gohr '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />'; 6920cecf9d5Sandi } else { 693a2d649c4Sandi $this->doc .= $this->_xmlEntities($smiley); 6940cecf9d5Sandi } 6950cecf9d5Sandi } 6960cecf9d5Sandi 6973dd5c225SAndreas Gohr /** 6983dd5c225SAndreas Gohr * Format an entity 6993dd5c225SAndreas Gohr * 7003dd5c225SAndreas Gohr * Entities are basically small text replacements 7013dd5c225SAndreas Gohr * 7023dd5c225SAndreas Gohr * Uses $this->entities 7033dd5c225SAndreas Gohr * 7043dd5c225SAndreas Gohr * @param string $entity 7054de671bcSandi */ 706de369923SAndreas Gohr public function entity($entity) { 7070cecf9d5Sandi if(array_key_exists($entity, $this->entities)) { 708a2d649c4Sandi $this->doc .= $this->entities[$entity]; 7090cecf9d5Sandi } else { 710a2d649c4Sandi $this->doc .= $this->_xmlEntities($entity); 7110cecf9d5Sandi } 7120cecf9d5Sandi } 7130cecf9d5Sandi 7143dd5c225SAndreas Gohr /** 7153dd5c225SAndreas Gohr * Typographically format a multiply sign 7163dd5c225SAndreas Gohr * 7173dd5c225SAndreas Gohr * Example: ($x=640, $y=480) should result in "640×480" 7183dd5c225SAndreas Gohr * 7193dd5c225SAndreas Gohr * @param string|int $x first value 7203dd5c225SAndreas Gohr * @param string|int $y second value 7213dd5c225SAndreas Gohr */ 722de369923SAndreas Gohr public function multiplyentity($x, $y) { 723a2d649c4Sandi $this->doc .= "$x×$y"; 7240cecf9d5Sandi } 7250cecf9d5Sandi 7263dd5c225SAndreas Gohr /** 7273dd5c225SAndreas Gohr * Render an opening single quote char (language specific) 7283dd5c225SAndreas Gohr */ 729de369923SAndreas Gohr public function singlequoteopening() { 73071b40da2SAnika Henke global $lang; 73171b40da2SAnika Henke $this->doc .= $lang['singlequoteopening']; 7320cecf9d5Sandi } 7330cecf9d5Sandi 7343dd5c225SAndreas Gohr /** 7353dd5c225SAndreas Gohr * Render a closing single quote char (language specific) 7363dd5c225SAndreas Gohr */ 737de369923SAndreas Gohr public function singlequoteclosing() { 73871b40da2SAnika Henke global $lang; 73971b40da2SAnika Henke $this->doc .= $lang['singlequoteclosing']; 7400cecf9d5Sandi } 7410cecf9d5Sandi 7423dd5c225SAndreas Gohr /** 7433dd5c225SAndreas Gohr * Render an apostrophe char (language specific) 7443dd5c225SAndreas Gohr */ 745de369923SAndreas Gohr public function apostrophe() { 74657d757d1SAndreas Gohr global $lang; 747a8bd192aSAndreas Gohr $this->doc .= $lang['apostrophe']; 74857d757d1SAndreas Gohr } 74957d757d1SAndreas Gohr 7503dd5c225SAndreas Gohr /** 7513dd5c225SAndreas Gohr * Render an opening double quote char (language specific) 7523dd5c225SAndreas Gohr */ 753de369923SAndreas Gohr public function doublequoteopening() { 75471b40da2SAnika Henke global $lang; 75571b40da2SAnika Henke $this->doc .= $lang['doublequoteopening']; 7560cecf9d5Sandi } 7570cecf9d5Sandi 7583dd5c225SAndreas Gohr /** 7593dd5c225SAndreas Gohr * Render an closinging double quote char (language specific) 7603dd5c225SAndreas Gohr */ 761de369923SAndreas Gohr public function doublequoteclosing() { 76271b40da2SAnika Henke global $lang; 76371b40da2SAnika Henke $this->doc .= $lang['doublequoteclosing']; 7640cecf9d5Sandi } 7650cecf9d5Sandi 7660cecf9d5Sandi /** 7673dd5c225SAndreas Gohr * Render a CamelCase link 7683dd5c225SAndreas Gohr * 7693dd5c225SAndreas Gohr * @param string $link The link name 770122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 7710c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 7720c4c0281SGerrit Uitslag * 7733dd5c225SAndreas Gohr * @see http://en.wikipedia.org/wiki/CamelCase 7740cecf9d5Sandi */ 775de369923SAndreas Gohr public function camelcaselink($link, $returnonly = false) { 776122f2d46SAndreas Böhler if($returnonly) { 777122f2d46SAndreas Böhler return $this->internallink($link, $link, null, true); 778122f2d46SAndreas Böhler } else { 77911d0aa47Sandi $this->internallink($link, $link); 7800cecf9d5Sandi } 781122f2d46SAndreas Böhler } 7820cecf9d5Sandi 7833dd5c225SAndreas Gohr /** 7843dd5c225SAndreas Gohr * Render a page local link 7853dd5c225SAndreas Gohr * 7863dd5c225SAndreas Gohr * @param string $hash hash link identifier 7873dd5c225SAndreas Gohr * @param string $name name for the link 788122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 7890c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 7903dd5c225SAndreas Gohr */ 791de369923SAndreas Gohr public function locallink($hash, $name = null, $returnonly = false) { 7920b7c14c2Sandi global $ID; 7930b7c14c2Sandi $name = $this->_getLinkTitle($name, $hash, $isImage); 7940b7c14c2Sandi $hash = $this->_headerToLink($hash); 795e260f93bSAnika Henke $title = $ID.' ↵'; 796122f2d46SAndreas Böhler 797122f2d46SAndreas Böhler $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 798122f2d46SAndreas Böhler $doc .= $name; 799122f2d46SAndreas Böhler $doc .= '</a>'; 800122f2d46SAndreas Böhler 801122f2d46SAndreas Böhler if($returnonly) { 802122f2d46SAndreas Böhler return $doc; 803122f2d46SAndreas Böhler } else { 804122f2d46SAndreas Böhler $this->doc .= $doc; 805122f2d46SAndreas Böhler } 8060b7c14c2Sandi } 8070b7c14c2Sandi 808cffcc403Sandi /** 8093fd0b676Sandi * Render an internal Wiki Link 8103fd0b676Sandi * 811fe9ec250SChris Smith * $search,$returnonly & $linktype are not for the renderer but are used 812cffcc403Sandi * elsewhere - no need to implement them in other renderers 8133fd0b676Sandi * 8143dd5c225SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 815f23eef27SGerrit Uitslag * @param string $id pageid 816f23eef27SGerrit Uitslag * @param string|null $name link name 817f23eef27SGerrit Uitslag * @param string|null $search adds search url param 818f23eef27SGerrit Uitslag * @param bool $returnonly whether to return html or write to doc attribute 819f23eef27SGerrit Uitslag * @param string $linktype type to set use of headings 820f23eef27SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 821cffcc403Sandi */ 822de369923SAndreas Gohr public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { 823ba11bd29Sandi global $conf; 82437e34a5eSandi global $ID; 825c4dda6afSAnika Henke global $INFO; 82644653a53SAdrian Lang 8273d5e07d9SAdrian Lang $params = ''; 8283d5e07d9SAdrian Lang $parts = explode('?', $id, 2); 8293d5e07d9SAdrian Lang if(count($parts) === 2) { 8303d5e07d9SAdrian Lang $id = $parts[0]; 8313d5e07d9SAdrian Lang $params = $parts[1]; 83244653a53SAdrian Lang } 83344653a53SAdrian Lang 834fda14ffcSIzidor Matušov // For empty $id we need to know the current $ID 835fda14ffcSIzidor Matušov // We need this check because _simpleTitle needs 836fda14ffcSIzidor Matušov // correct $id and resolve_pageid() use cleanID($id) 837fda14ffcSIzidor Matušov // (some things could be lost) 838fda14ffcSIzidor Matušov if($id === '') { 839fda14ffcSIzidor Matušov $id = $ID; 840fda14ffcSIzidor Matušov } 841fda14ffcSIzidor Matušov 8420339c872Sjan // default name is based on $id as given 8430339c872Sjan $default = $this->_simpleTitle($id); 844ad32e47eSAndreas Gohr 8450339c872Sjan // now first resolve and clean up the $id 8468c6be208SAndreas Gohr $id = (new PageResolver($ID))->resolveId($id, $this->date_at, true); 8478c6be208SAndreas Gohr $exists = page_exists($id, $this->date_at, false, true); 848fda14ffcSIzidor Matušov 84959bc3b48SGerrit Uitslag $link = array(); 850fe9ec250SChris Smith $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 8510e1c636eSandi if(!$isImage) { 8520e1c636eSandi if($exists) { 853ba11bd29Sandi $class = 'wikilink1'; 8540cecf9d5Sandi } else { 855ba11bd29Sandi $class = 'wikilink2'; 85644a6b4c7SAndreas Gohr $link['rel'] = 'nofollow'; 8570cecf9d5Sandi } 8580cecf9d5Sandi } else { 859ba11bd29Sandi $class = 'media'; 8600cecf9d5Sandi } 8610cecf9d5Sandi 862a1685bedSandi //keep hash anchor 863ec34bb30SAndreas Gohr list($id, $hash) = sexplode('#', $id, 2); 864943dedc6SAndreas Gohr if(!empty($hash)) $hash = $this->_headerToLink($hash); 865a1685bedSandi 866ba11bd29Sandi //prepare for formating 867ba11bd29Sandi $link['target'] = $conf['target']['wiki']; 868ba11bd29Sandi $link['style'] = ''; 869ba11bd29Sandi $link['pre'] = ''; 870ba11bd29Sandi $link['suf'] = ''; 871bbac1489SPhy $link['more'] = 'data-wiki-id="'.$id.'"'; // id is already cleaned 872ba11bd29Sandi $link['class'] = $class; 8735c2eed9aSlisps if($this->date_at) { 874912a6d48SPhy $params = $params.'&at='.rawurlencode($this->date_at); 8755c2eed9aSlisps } 87644653a53SAdrian Lang $link['url'] = wl($id, $params); 877ba11bd29Sandi $link['name'] = $name; 878ba11bd29Sandi $link['title'] = $id; 879723d78dbSandi //add search string 880723d78dbSandi if($search) { 881546d3a99SAndreas Gohr ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 882546d3a99SAndreas Gohr if(is_array($search)) { 883546d3a99SAndreas Gohr $search = array_map('rawurlencode', $search); 884546d3a99SAndreas Gohr $link['url'] .= 's[]='.join('&s[]=', $search); 885546d3a99SAndreas Gohr } else { 886546d3a99SAndreas Gohr $link['url'] .= 's='.rawurlencode($search); 887546d3a99SAndreas Gohr } 888723d78dbSandi } 889723d78dbSandi 890a1685bedSandi //keep hash 891a1685bedSandi if($hash) $link['url'] .= '#'.$hash; 892a1685bedSandi 893ba11bd29Sandi //output formatted 894cffcc403Sandi if($returnonly) { 895cffcc403Sandi return $this->_formatLink($link); 896cffcc403Sandi } else { 897a2d649c4Sandi $this->doc .= $this->_formatLink($link); 8980cecf9d5Sandi } 899cffcc403Sandi } 9000cecf9d5Sandi 9013dd5c225SAndreas Gohr /** 9023dd5c225SAndreas Gohr * Render an external link 9033dd5c225SAndreas Gohr * 9043dd5c225SAndreas Gohr * @param string $url full URL with scheme 9053dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 906122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9070c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9083dd5c225SAndreas Gohr */ 909de369923SAndreas Gohr public function externallink($url, $name = null, $returnonly = false) { 910b625487dSandi global $conf; 9110cecf9d5Sandi 912433bef32Sandi $name = $this->_getLinkTitle($name, $url, $isImage); 9136f0c5dbfSandi 914b52b1596SAndreas Gohr // url might be an attack vector, only allow registered protocols 915b52b1596SAndreas Gohr if(is_null($this->schemes)) $this->schemes = getSchemes(); 916b52b1596SAndreas Gohr list($scheme) = explode('://', $url); 917b52b1596SAndreas Gohr $scheme = strtolower($scheme); 918b52b1596SAndreas Gohr if(!in_array($scheme, $this->schemes)) $url = ''; 919b52b1596SAndreas Gohr 920b52b1596SAndreas Gohr // is there still an URL? 921b52b1596SAndreas Gohr if(!$url) { 92249cef4fdSAndreas Böhler if($returnonly) { 92349cef4fdSAndreas Böhler return $name; 92449cef4fdSAndreas Böhler } else { 925b52b1596SAndreas Gohr $this->doc .= $name; 92649cef4fdSAndreas Böhler } 927b52b1596SAndreas Gohr return; 928b52b1596SAndreas Gohr } 929b52b1596SAndreas Gohr 930b52b1596SAndreas Gohr // set class 9310cecf9d5Sandi if(!$isImage) { 932b625487dSandi $class = 'urlextern'; 9330cecf9d5Sandi } else { 934b625487dSandi $class = 'media'; 9350cecf9d5Sandi } 9360cecf9d5Sandi 937b625487dSandi //prepare for formating 93859bc3b48SGerrit Uitslag $link = array(); 939b625487dSandi $link['target'] = $conf['target']['extern']; 940b625487dSandi $link['style'] = ''; 941b625487dSandi $link['pre'] = ''; 942b625487dSandi $link['suf'] = ''; 9435e163278SAndreas Gohr $link['more'] = ''; 944b625487dSandi $link['class'] = $class; 945b625487dSandi $link['url'] = $url; 946914045f3SAndreas Gohr $link['rel'] = ''; 947e1c10e4dSchris 948b625487dSandi $link['name'] = $name; 949433bef32Sandi $link['title'] = $this->_xmlEntities($url); 9505ddd0bbbSStarArmy if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; 951914045f3SAndreas Gohr if($conf['target']['extern']) $link['rel'] .= ' noopener'; 9520cecf9d5Sandi 953b625487dSandi //output formatted 954122f2d46SAndreas Böhler if($returnonly) { 955122f2d46SAndreas Böhler return $this->_formatLink($link); 956122f2d46SAndreas Böhler } else { 957a2d649c4Sandi $this->doc .= $this->_formatLink($link); 9580cecf9d5Sandi } 959122f2d46SAndreas Böhler } 9600cecf9d5Sandi 9610cecf9d5Sandi /** 9623dd5c225SAndreas Gohr * Render an interwiki link 9633dd5c225SAndreas Gohr * 9643dd5c225SAndreas Gohr * You may want to use $this->_resolveInterWiki() here 9653dd5c225SAndreas Gohr * 9663dd5c225SAndreas Gohr * @param string $match original link - probably not much use 9673dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 9683dd5c225SAndreas Gohr * @param string $wikiName indentifier (shortcut) for the remote wiki 9693dd5c225SAndreas Gohr * @param string $wikiUri the fragment parsed from the original link 970122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9710c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9720cecf9d5Sandi */ 973de369923SAndreas Gohr public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { 974b625487dSandi global $conf; 9750cecf9d5Sandi 97697a3e4e3Sandi $link = array(); 97797a3e4e3Sandi $link['target'] = $conf['target']['interwiki']; 97897a3e4e3Sandi $link['pre'] = ''; 97997a3e4e3Sandi $link['suf'] = ''; 9805e163278SAndreas Gohr $link['more'] = ''; 981433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 982914045f3SAndreas Gohr $link['rel'] = ''; 9830cecf9d5Sandi 98497a3e4e3Sandi //get interwiki URL 9856496c33fSGerrit Uitslag $exists = null; 9866496c33fSGerrit Uitslag $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 9870cecf9d5Sandi 98897a3e4e3Sandi if(!$isImage) { 9899d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 9909d2ddea4SAndreas Gohr $link['class'] = "interwiki iw_$class"; 9911c2d1019SAndreas Gohr } else { 9921c2d1019SAndreas Gohr $link['class'] = 'media'; 99397a3e4e3Sandi } 9940cecf9d5Sandi 99597a3e4e3Sandi //do we stay at the same server? Use local target 9962345e871SGerrit Uitslag if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 99797a3e4e3Sandi $link['target'] = $conf['target']['wiki']; 99897a3e4e3Sandi } 9996496c33fSGerrit Uitslag if($exists !== null && !$isImage) { 10006496c33fSGerrit Uitslag if($exists) { 10016496c33fSGerrit Uitslag $link['class'] .= ' wikilink1'; 10026496c33fSGerrit Uitslag } else { 10036496c33fSGerrit Uitslag $link['class'] .= ' wikilink2'; 1004914045f3SAndreas Gohr $link['rel'] .= ' nofollow'; 10056496c33fSGerrit Uitslag } 10066496c33fSGerrit Uitslag } 1007914045f3SAndreas Gohr if($conf['target']['interwiki']) $link['rel'] .= ' noopener'; 10080cecf9d5Sandi 100997a3e4e3Sandi $link['url'] = $url; 1010f7711f2bSAndreas Gohr $link['title'] = $this->_xmlEntities($link['url']); 101197a3e4e3Sandi 101297a3e4e3Sandi // output formatted 1013122f2d46SAndreas Böhler if($returnonly) { 1014abde5980SPhy if($url == '') return $link['name']; 1015122f2d46SAndreas Böhler return $this->_formatLink($link); 1016122f2d46SAndreas Böhler } else { 1017abde5980SPhy if($url == '') $this->doc .= $link['name']; 1018abde5980SPhy else $this->doc .= $this->_formatLink($link); 10190cecf9d5Sandi } 1020122f2d46SAndreas Böhler } 10210cecf9d5Sandi 10220cecf9d5Sandi /** 10233dd5c225SAndreas Gohr * Link to windows share 10243dd5c225SAndreas Gohr * 10253dd5c225SAndreas Gohr * @param string $url the link 10263dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1027122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10280c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10290cecf9d5Sandi */ 1030de369923SAndreas Gohr public function windowssharelink($url, $name = null, $returnonly = false) { 10311d47afe1Sandi global $conf; 10323dd5c225SAndreas Gohr 10331d47afe1Sandi //simple setup 103459bc3b48SGerrit Uitslag $link = array(); 10351d47afe1Sandi $link['target'] = $conf['target']['windows']; 10361d47afe1Sandi $link['pre'] = ''; 10371d47afe1Sandi $link['suf'] = ''; 10381d47afe1Sandi $link['style'] = ''; 10390cecf9d5Sandi 1040433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 10410cecf9d5Sandi if(!$isImage) { 10421d47afe1Sandi $link['class'] = 'windows'; 10430cecf9d5Sandi } else { 10441d47afe1Sandi $link['class'] = 'media'; 10450cecf9d5Sandi } 10460cecf9d5Sandi 1047433bef32Sandi $link['title'] = $this->_xmlEntities($url); 10481d47afe1Sandi $url = str_replace('\\', '/', $url); 10491d47afe1Sandi $url = 'file:///'.$url; 10501d47afe1Sandi $link['url'] = $url; 10510cecf9d5Sandi 10521d47afe1Sandi //output formatted 1053122f2d46SAndreas Böhler if($returnonly) { 1054122f2d46SAndreas Böhler return $this->_formatLink($link); 1055122f2d46SAndreas Böhler } else { 1056a2d649c4Sandi $this->doc .= $this->_formatLink($link); 10570cecf9d5Sandi } 1058122f2d46SAndreas Böhler } 10590cecf9d5Sandi 10603dd5c225SAndreas Gohr /** 10613dd5c225SAndreas Gohr * Render a linked E-Mail Address 10623dd5c225SAndreas Gohr * 10633dd5c225SAndreas Gohr * Honors $conf['mailguard'] setting 10643dd5c225SAndreas Gohr * 10653dd5c225SAndreas Gohr * @param string $address Email-Address 10663dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1067122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10680c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10693dd5c225SAndreas Gohr */ 1070de369923SAndreas Gohr public function emaillink($address, $name = null, $returnonly = false) { 107171352defSandi global $conf; 107271352defSandi //simple setup 107371352defSandi $link = array(); 107471352defSandi $link['target'] = ''; 107571352defSandi $link['pre'] = ''; 107671352defSandi $link['suf'] = ''; 107771352defSandi $link['style'] = ''; 107871352defSandi $link['more'] = ''; 10790cecf9d5Sandi 1080c078fc55SAndreas Gohr $name = $this->_getLinkTitle($name, '', $isImage); 10810cecf9d5Sandi if(!$isImage) { 1082be96545cSAnika Henke $link['class'] = 'mail'; 10830cecf9d5Sandi } else { 1084be96545cSAnika Henke $link['class'] = 'media'; 10850cecf9d5Sandi } 10860cecf9d5Sandi 108707738714SAndreas Gohr $address = $this->_xmlEntities($address); 108800a7b5adSEsther Brunner $address = obfuscate($address); 108900a7b5adSEsther Brunner $title = $address; 10908c128049SAndreas Gohr 109171352defSandi if(empty($name)) { 109200a7b5adSEsther Brunner $name = $address; 109371352defSandi } 10940cecf9d5Sandi 1095776b36ecSAndreas Gohr if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1096776b36ecSAndreas Gohr 1097776b36ecSAndreas Gohr $link['url'] = 'mailto:'.$address; 109871352defSandi $link['name'] = $name; 109971352defSandi $link['title'] = $title; 11000cecf9d5Sandi 110171352defSandi //output formatted 1102122f2d46SAndreas Böhler if($returnonly) { 1103122f2d46SAndreas Böhler return $this->_formatLink($link); 1104122f2d46SAndreas Böhler } else { 1105a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11060cecf9d5Sandi } 1107122f2d46SAndreas Böhler } 11080cecf9d5Sandi 11093dd5c225SAndreas Gohr /** 11103dd5c225SAndreas Gohr * Render an internal media file 11113dd5c225SAndreas Gohr * 11123dd5c225SAndreas Gohr * @param string $src media ID 11133dd5c225SAndreas Gohr * @param string $title descriptive text 11143dd5c225SAndreas Gohr * @param string $align left|center|right 11153dd5c225SAndreas Gohr * @param int $width width of media in pixel 11163dd5c225SAndreas Gohr * @param int $height height of media in pixel 11173dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11183dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 11193dd5c225SAndreas Gohr * @param bool $return return HTML instead of adding to $doc 11200c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11213dd5c225SAndreas Gohr */ 1122de369923SAndreas Gohr public function internalmedia($src, $title = null, $align = null, $width = null, 11233dd5c225SAndreas Gohr $height = null, $cache = null, $linking = null, $return = false) { 112437e34a5eSandi global $ID; 11258f34cf3dSMichael Große if (strpos($src, '#') !== false) { 1126ec34bb30SAndreas Gohr list($src, $hash) = sexplode('#', $src, 2); 11278f34cf3dSMichael Große } 11288c6be208SAndreas Gohr $src = (new MediaResolver($ID))->resolveId($src,$this->date_at,true); 11298c6be208SAndreas Gohr $exists = media_exists($src); 11300cecf9d5Sandi 1131d98d4540SBen Coburn $noLink = false; 11328acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1133b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 11343685f775Sandi 11353dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1136b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 113764159a61SAndreas Gohr $link['url'] = ml( 113864159a61SAndreas Gohr $src, 113964159a61SAndreas Gohr array( 114064159a61SAndreas Gohr 'id' => $ID, 114164159a61SAndreas Gohr 'cache' => $cache, 114264159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 114364159a61SAndreas Gohr ), 114464159a61SAndreas Gohr ($linking == 'direct') 114564159a61SAndreas Gohr ); 1146f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 11472a2a2ba2SAnika Henke // don't link movies 114844881bd0Shenning.noren $noLink = true; 114955efc227SAndreas Gohr } else { 11502ca14335SEsther Brunner // add file icons 11519d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 11529d2ddea4SAndreas Gohr $link['class'] .= ' mediafile mf_'.$class; 115364159a61SAndreas Gohr $link['url'] = ml( 115464159a61SAndreas Gohr $src, 115564159a61SAndreas Gohr array( 115664159a61SAndreas Gohr 'id' => $ID, 115764159a61SAndreas Gohr 'cache' => $cache, 115864159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 115964159a61SAndreas Gohr ), 116064159a61SAndreas Gohr true 116164159a61SAndreas Gohr ); 116291328684SMichael Hamann if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; 116355efc227SAndreas Gohr } 11643685f775Sandi 11658f34cf3dSMichael Große if (!empty($hash)) $link['url'] .= '#'.$hash; 116691df343aSAndreas Gohr 11676fe20453SGina Haeussge //markup non existing files 11684a24b459SKate Arzamastseva if(!$exists) { 11696fe20453SGina Haeussge $link['class'] .= ' wikilink2'; 11704a24b459SKate Arzamastseva } 11716fe20453SGina Haeussge 11723685f775Sandi //output formatted 1173f50634f0SAnika Henke if($return) { 1174f50634f0SAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1175f50634f0SAnika Henke else return $this->_formatLink($link); 1176f50634f0SAnika Henke } else { 1177dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 11782ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 11790cecf9d5Sandi } 1180f50634f0SAnika Henke } 11810cecf9d5Sandi 11823dd5c225SAndreas Gohr /** 11833dd5c225SAndreas Gohr * Render an external media file 11843dd5c225SAndreas Gohr * 11853dd5c225SAndreas Gohr * @param string $src full media URL 11863dd5c225SAndreas Gohr * @param string $title descriptive text 11873dd5c225SAndreas Gohr * @param string $align left|center|right 11883dd5c225SAndreas Gohr * @param int $width width of media in pixel 11893dd5c225SAndreas Gohr * @param int $height height of media in pixel 11903dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11913dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 1192410ee62aSAnika Henke * @param bool $return return HTML instead of adding to $doc 11930c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11943dd5c225SAndreas Gohr */ 1195de369923SAndreas Gohr public function externalmedia($src, $title = null, $align = null, $width = null, 1196410ee62aSAnika Henke $height = null, $cache = null, $linking = null, $return = false) { 11976efc45a2SDmitry Katsubo if(link_isinterwiki($src)){ 1198ec34bb30SAndreas Gohr list($shortcut, $reference) = sexplode('>', $src, 2, ''); 11996efc45a2SDmitry Katsubo $exists = null; 12006efc45a2SDmitry Katsubo $src = $this->_resolveInterWiki($shortcut, $reference, $exists); 1201abde5980SPhy if($src == '' && empty($title)){ 1202abde5980SPhy // make sure at least something will be shown in this case 1203abde5980SPhy $title = $reference; 1204abde5980SPhy } 12056efc45a2SDmitry Katsubo } 1206ec34bb30SAndreas Gohr list($src, $hash) = sexplode('#', $src, 2); 1207d98d4540SBen Coburn $noLink = false; 1208abde5980SPhy if($src == '') { 1209abde5980SPhy // only output plaintext without link if there is no src 1210abde5980SPhy $noLink = true; 1211abde5980SPhy } 12128acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1213b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1214b739ff0fSPierre Spring 1215b739ff0fSPierre Spring $link['url'] = ml($src, array('cache' => $cache)); 12163685f775Sandi 12173dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1218b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 12192ca14335SEsther Brunner // link only jpeg images 122044881bd0Shenning.noren // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1221f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12222a2a2ba2SAnika Henke // don't link movies 122344881bd0Shenning.noren $noLink = true; 12242ca14335SEsther Brunner } else { 12252ca14335SEsther Brunner // add file icons 122627bf7924STom N Harris $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 122727bf7924STom N Harris $link['class'] .= ' mediafile mf_'.$class; 12282ca14335SEsther Brunner } 12292ca14335SEsther Brunner 123091df343aSAndreas Gohr if($hash) $link['url'] .= '#'.$hash; 123191df343aSAndreas Gohr 12323685f775Sandi //output formatted 1233410ee62aSAnika Henke if($return) { 1234410ee62aSAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1235410ee62aSAnika Henke else return $this->_formatLink($link); 1236410ee62aSAnika Henke } else { 1237dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 12382ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 12390cecf9d5Sandi } 1240410ee62aSAnika Henke } 12410cecf9d5Sandi 12424826ab45Sandi /** 12433db95becSAndreas Gohr * Renders an RSS feed 1244b625487dSandi * 12450c4c0281SGerrit Uitslag * @param string $url URL of the feed 12460c4c0281SGerrit Uitslag * @param array $params Finetuning of the output 12470c4c0281SGerrit Uitslag * 1248b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 1249b625487dSandi */ 1250de369923SAndreas Gohr public function rss($url, $params) { 1251b625487dSandi global $lang; 12523db95becSAndreas Gohr global $conf; 12533db95becSAndreas Gohr 12543db95becSAndreas Gohr require_once(DOKU_INC.'inc/FeedParser.php'); 12553db95becSAndreas Gohr $feed = new FeedParser(); 125600077af8SAndreas Gohr $feed->set_feed_url($url); 1257b625487dSandi 1258b625487dSandi //disable warning while fetching 12593dd5c225SAndreas Gohr if(!defined('DOKU_E_LEVEL')) { 12603dd5c225SAndreas Gohr $elvl = error_reporting(E_ERROR); 12613dd5c225SAndreas Gohr } 12623db95becSAndreas Gohr $rc = $feed->init(); 12633dd5c225SAndreas Gohr if(isset($elvl)) { 12643dd5c225SAndreas Gohr error_reporting($elvl); 12653dd5c225SAndreas Gohr } 1266b625487dSandi 126738c6f603SRobin H. Johnson if($params['nosort']) $feed->enable_order_by_date(false); 126838c6f603SRobin H. Johnson 12693db95becSAndreas Gohr //decide on start and end 12703db95becSAndreas Gohr if($params['reverse']) { 12713db95becSAndreas Gohr $mod = -1; 12723db95becSAndreas Gohr $start = $feed->get_item_quantity() - 1; 12733db95becSAndreas Gohr $end = $start - ($params['max']); 1274b2a412b0SAndreas Gohr $end = ($end < -1) ? -1 : $end; 12753db95becSAndreas Gohr } else { 12763db95becSAndreas Gohr $mod = 1; 12773db95becSAndreas Gohr $start = 0; 12783db95becSAndreas Gohr $end = $feed->get_item_quantity(); 1279d91ab76fSMatt Perry $end = ($end > $params['max']) ? $params['max'] : $end; 12803db95becSAndreas Gohr } 12813db95becSAndreas Gohr 1282a2d649c4Sandi $this->doc .= '<ul class="rss">'; 12833db95becSAndreas Gohr if($rc) { 12843db95becSAndreas Gohr for($x = $start; $x != $end; $x += $mod) { 12851bde1582SAndreas Gohr $item = $feed->get_item($x); 12863db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1287d2ea3363SAndreas Gohr // support feeds without links 1288d2ea3363SAndreas Gohr $lnkurl = $item->get_permalink(); 1289d2ea3363SAndreas Gohr if($lnkurl) { 1290793361f8SAndreas Gohr // title is escaped by SimplePie, we unescape here because it 1291793361f8SAndreas Gohr // is escaped again in externallink() FS#1705 12923dd5c225SAndreas Gohr $this->externallink( 12933dd5c225SAndreas Gohr $item->get_permalink(), 12943dd5c225SAndreas Gohr html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') 12953dd5c225SAndreas Gohr ); 1296d2ea3363SAndreas Gohr } else { 1297d2ea3363SAndreas Gohr $this->doc .= ' '.$item->get_title(); 1298d2ea3363SAndreas Gohr } 12993db95becSAndreas Gohr if($params['author']) { 13001bde1582SAndreas Gohr $author = $item->get_author(0); 13011bde1582SAndreas Gohr if($author) { 13021bde1582SAndreas Gohr $name = $author->get_name(); 13031bde1582SAndreas Gohr if(!$name) $name = $author->get_email(); 1304163c2842SPhy if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name); 13051bde1582SAndreas Gohr } 13063db95becSAndreas Gohr } 13073db95becSAndreas Gohr if($params['date']) { 13082e7e0c29SAndreas Gohr $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 13093db95becSAndreas Gohr } 13101bde1582SAndreas Gohr if($params['details']) { 13113db95becSAndreas Gohr $this->doc .= '<div class="detail">'; 13121bde1582SAndreas Gohr $this->doc .= strip_tags($item->get_description()); 13133db95becSAndreas Gohr $this->doc .= '</div>'; 13143db95becSAndreas Gohr } 13153db95becSAndreas Gohr 13163db95becSAndreas Gohr $this->doc .= '</div></li>'; 1317b625487dSandi } 1318b625487dSandi } else { 13193db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1320a2d649c4Sandi $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 1321b625487dSandi $this->externallink($url); 132245e147ccSAndreas Gohr if($conf['allowdebug']) { 132345e147ccSAndreas Gohr $this->doc .= '<!--'.hsc($feed->error).'-->'; 132445e147ccSAndreas Gohr } 13253db95becSAndreas Gohr $this->doc .= '</div></li>'; 1326b625487dSandi } 1327a2d649c4Sandi $this->doc .= '</ul>'; 1328b625487dSandi } 1329b625487dSandi 13303dd5c225SAndreas Gohr /** 13313dd5c225SAndreas Gohr * Start a table 13323dd5c225SAndreas Gohr * 13333dd5c225SAndreas Gohr * @param int $maxcols maximum number of columns 13343dd5c225SAndreas Gohr * @param int $numrows NOT IMPLEMENTED 13353dd5c225SAndreas Gohr * @param int $pos byte position in the original source 13367d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 13373dd5c225SAndreas Gohr */ 1338de369923SAndreas Gohr public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { 1339b5742cedSPierre Spring // initialize the row counter used for classes 1340b5742cedSPierre Spring $this->_counter['row_counter'] = 0; 1341619736fdSAdrian Lang $class = 'table'; 13420c4c0281SGerrit Uitslag if($classes !== null) { 13432e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 13440c4c0281SGerrit Uitslag $class .= ' ' . $classes; 13450c4c0281SGerrit Uitslag } 1346619736fdSAdrian Lang if($pos !== null) { 134706917fceSMichael Große $hid = $this->_headerToLink($class, true); 1348ec57f119SLarsDW223 $data = array(); 1349ec57f119SLarsDW223 $data['target'] = 'table'; 1350ec57f119SLarsDW223 $data['name'] = ''; 1351ec57f119SLarsDW223 $data['hid'] = $hid; 1352ec57f119SLarsDW223 $class .= ' '.$this->startSectionEdit($pos, $data); 1353619736fdSAdrian Lang } 1354619736fdSAdrian Lang $this->doc .= '<div class="'.$class.'"><table class="inline">'. 1355619736fdSAdrian Lang DOKU_LF; 13560cecf9d5Sandi } 13570cecf9d5Sandi 13583dd5c225SAndreas Gohr /** 13593dd5c225SAndreas Gohr * Close a table 13603dd5c225SAndreas Gohr * 13613dd5c225SAndreas Gohr * @param int $pos byte position in the original source 13623dd5c225SAndreas Gohr */ 1363de369923SAndreas Gohr public function table_close($pos = null) { 1364a8574918SAnika Henke $this->doc .= '</table></div>'.DOKU_LF; 1365619736fdSAdrian Lang if($pos !== null) { 136690df9a4dSAdrian Lang $this->finishSectionEdit($pos); 13670cecf9d5Sandi } 1368619736fdSAdrian Lang } 13690cecf9d5Sandi 13703dd5c225SAndreas Gohr /** 13713dd5c225SAndreas Gohr * Open a table header 13723dd5c225SAndreas Gohr */ 1373de369923SAndreas Gohr public function tablethead_open() { 1374f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 1375f05a1cc5SGerrit Uitslag } 1376f05a1cc5SGerrit Uitslag 13773dd5c225SAndreas Gohr /** 13783dd5c225SAndreas Gohr * Close a table header 13793dd5c225SAndreas Gohr */ 1380de369923SAndreas Gohr public function tablethead_close() { 1381f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 1382f05a1cc5SGerrit Uitslag } 1383f05a1cc5SGerrit Uitslag 13843dd5c225SAndreas Gohr /** 13855a93f869SAnika Henke * Open a table body 13865a93f869SAnika Henke */ 1387de369923SAndreas Gohr public function tabletbody_open() { 13885a93f869SAnika Henke $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF; 13895a93f869SAnika Henke } 13905a93f869SAnika Henke 13915a93f869SAnika Henke /** 13925a93f869SAnika Henke * Close a table body 13935a93f869SAnika Henke */ 1394de369923SAndreas Gohr public function tabletbody_close() { 13955a93f869SAnika Henke $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF; 13965a93f869SAnika Henke } 13975a93f869SAnika Henke 13985a93f869SAnika Henke /** 1399d2a99739SAndreas Gohr * Open a table footer 1400d2a99739SAndreas Gohr */ 1401de369923SAndreas Gohr public function tabletfoot_open() { 140244f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF; 1403d2a99739SAndreas Gohr } 1404d2a99739SAndreas Gohr 1405d2a99739SAndreas Gohr /** 1406d2a99739SAndreas Gohr * Close a table footer 1407d2a99739SAndreas Gohr */ 1408de369923SAndreas Gohr public function tabletfoot_close() { 140944f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF; 1410d2a99739SAndreas Gohr } 1411d2a99739SAndreas Gohr 1412d2a99739SAndreas Gohr /** 14133dd5c225SAndreas Gohr * Open a table row 14140c4c0281SGerrit Uitslag * 14157d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14163dd5c225SAndreas Gohr */ 1417de369923SAndreas Gohr public function tablerow_open($classes = null) { 1418b5742cedSPierre Spring // initialize the cell counter used for classes 1419b5742cedSPierre Spring $this->_counter['cell_counter'] = 0; 1420b5742cedSPierre Spring $class = 'row'.$this->_counter['row_counter']++; 14210c4c0281SGerrit Uitslag if($classes !== null) { 14222e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14230c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14240c4c0281SGerrit Uitslag } 1425b5742cedSPierre Spring $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; 14260cecf9d5Sandi } 14270cecf9d5Sandi 14283dd5c225SAndreas Gohr /** 14293dd5c225SAndreas Gohr * Close a table row 14303dd5c225SAndreas Gohr */ 1431de369923SAndreas Gohr public function tablerow_close() { 1432a2d649c4Sandi $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 14330cecf9d5Sandi } 14340cecf9d5Sandi 14353dd5c225SAndreas Gohr /** 14363dd5c225SAndreas Gohr * Open a table header cell 14373dd5c225SAndreas Gohr * 14383dd5c225SAndreas Gohr * @param int $colspan 14393dd5c225SAndreas Gohr * @param string $align left|center|right 14403dd5c225SAndreas Gohr * @param int $rowspan 14417d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14423dd5c225SAndreas Gohr */ 1443de369923SAndreas Gohr public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1444b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 14450cecf9d5Sandi if(!is_null($align)) { 1446b5742cedSPierre Spring $class .= ' '.$align.'align'; 14470cecf9d5Sandi } 14480c4c0281SGerrit Uitslag if($classes !== null) { 14492e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14500c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14510c4c0281SGerrit Uitslag } 1452b5742cedSPierre Spring $class .= '"'; 1453b5742cedSPierre Spring $this->doc .= '<th '.$class; 14540cecf9d5Sandi if($colspan > 1) { 1455a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1456a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 14570cecf9d5Sandi } 145825b97867Shakan.sandell if($rowspan > 1) { 145925b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 146025b97867Shakan.sandell } 1461a2d649c4Sandi $this->doc .= '>'; 14620cecf9d5Sandi } 14630cecf9d5Sandi 14643dd5c225SAndreas Gohr /** 14653dd5c225SAndreas Gohr * Close a table header cell 14663dd5c225SAndreas Gohr */ 1467de369923SAndreas Gohr public function tableheader_close() { 1468a2d649c4Sandi $this->doc .= '</th>'; 14690cecf9d5Sandi } 14700cecf9d5Sandi 14713dd5c225SAndreas Gohr /** 14723dd5c225SAndreas Gohr * Open a table cell 14733dd5c225SAndreas Gohr * 14743dd5c225SAndreas Gohr * @param int $colspan 14753dd5c225SAndreas Gohr * @param string $align left|center|right 14763dd5c225SAndreas Gohr * @param int $rowspan 14777d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14783dd5c225SAndreas Gohr */ 1479de369923SAndreas Gohr public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1480b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 14810cecf9d5Sandi if(!is_null($align)) { 1482b5742cedSPierre Spring $class .= ' '.$align.'align'; 14830cecf9d5Sandi } 14840c4c0281SGerrit Uitslag if($classes !== null) { 14852e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14860c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14870c4c0281SGerrit Uitslag } 1488b5742cedSPierre Spring $class .= '"'; 1489b5742cedSPierre Spring $this->doc .= '<td '.$class; 14900cecf9d5Sandi if($colspan > 1) { 1491a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1492a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 14930cecf9d5Sandi } 149425b97867Shakan.sandell if($rowspan > 1) { 149525b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 149625b97867Shakan.sandell } 1497a2d649c4Sandi $this->doc .= '>'; 14980cecf9d5Sandi } 14990cecf9d5Sandi 15003dd5c225SAndreas Gohr /** 15013dd5c225SAndreas Gohr * Close a table cell 15023dd5c225SAndreas Gohr */ 1503de369923SAndreas Gohr public function tablecell_close() { 1504a2d649c4Sandi $this->doc .= '</td>'; 15050cecf9d5Sandi } 15060cecf9d5Sandi 1507cea664bdSLarsDW223 /** 1508cea664bdSLarsDW223 * Returns the current header level. 1509cea664bdSLarsDW223 * (required e.g. by the filelist plugin) 1510cea664bdSLarsDW223 * 1511cea664bdSLarsDW223 * @return int The current header level 1512cea664bdSLarsDW223 */ 1513de369923SAndreas Gohr public function getLastlevel() { 1514cea664bdSLarsDW223 return $this->lastlevel; 1515cea664bdSLarsDW223 } 1516cea664bdSLarsDW223 15173dd5c225SAndreas Gohr #region Utility functions 15180cecf9d5Sandi 1519ba11bd29Sandi /** 15203fd0b676Sandi * Build a link 15213fd0b676Sandi * 15223fd0b676Sandi * Assembles all parts defined in $link returns HTML for the link 1523ba11bd29Sandi * 15240c4c0281SGerrit Uitslag * @param array $link attributes of a link 15250c4c0281SGerrit Uitslag * @return string 15260c4c0281SGerrit Uitslag * 1527ba11bd29Sandi * @author Andreas Gohr <andi@splitbrain.org> 1528ba11bd29Sandi */ 1529de369923SAndreas Gohr public function _formatLink($link) { 1530ba11bd29Sandi //make sure the url is XHTML compliant (skip mailto) 1531ba11bd29Sandi if(substr($link['url'], 0, 7) != 'mailto:') { 1532ba11bd29Sandi $link['url'] = str_replace('&', '&', $link['url']); 1533ba11bd29Sandi $link['url'] = str_replace('&amp;', '&', $link['url']); 1534ba11bd29Sandi } 1535ba11bd29Sandi //remove double encodings in titles 1536ba11bd29Sandi $link['title'] = str_replace('&amp;', '&', $link['title']); 1537ba11bd29Sandi 1538453493f2SAndreas Gohr // be sure there are no bad chars in url or title 1539453493f2SAndreas Gohr // (we can't do this for name because it can contain an img tag) 1540453493f2SAndreas Gohr $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); 1541453493f2SAndreas Gohr $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); 1542453493f2SAndreas Gohr 1543ba11bd29Sandi $ret = ''; 1544ba11bd29Sandi $ret .= $link['pre']; 1545ba11bd29Sandi $ret .= '<a href="'.$link['url'].'"'; 1546bb4866bdSchris if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 1547bb4866bdSchris if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 1548bb4866bdSchris if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 1549bb4866bdSchris if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 1550914045f3SAndreas Gohr if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"'; 1551bb4866bdSchris if(!empty($link['more'])) $ret .= ' '.$link['more']; 1552ba11bd29Sandi $ret .= '>'; 1553ba11bd29Sandi $ret .= $link['name']; 1554ba11bd29Sandi $ret .= '</a>'; 1555ba11bd29Sandi $ret .= $link['suf']; 1556ba11bd29Sandi return $ret; 1557ba11bd29Sandi } 1558ba11bd29Sandi 1559ba11bd29Sandi /** 15603fd0b676Sandi * Renders internal and external media 15613fd0b676Sandi * 15623fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 15633dd5c225SAndreas Gohr * @param string $src media ID 15643dd5c225SAndreas Gohr * @param string $title descriptive text 15653dd5c225SAndreas Gohr * @param string $align left|center|right 15663dd5c225SAndreas Gohr * @param int $width width of media in pixel 15673dd5c225SAndreas Gohr * @param int $height height of media in pixel 15683dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 15693dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 15703dd5c225SAndreas Gohr * @return string 15713fd0b676Sandi */ 1572de369923SAndreas Gohr public function _media($src, $title = null, $align = null, $width = null, 15730ea51e63SMatt Perry $height = null, $cache = null, $render = true) { 15743fd0b676Sandi 15753fd0b676Sandi $ret = ''; 15763fd0b676Sandi 15773dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src); 15783fd0b676Sandi if(substr($mime, 0, 5) == 'image') { 1579b739ff0fSPierre Spring // first get the $title 1580b739ff0fSPierre Spring if(!is_null($title)) { 1581b739ff0fSPierre Spring $title = $this->_xmlEntities($title); 1582b739ff0fSPierre Spring } elseif($ext == 'jpg' || $ext == 'jpeg') { 1583b739ff0fSPierre Spring //try to use the caption from IPTC/EXIF 1584b739ff0fSPierre Spring require_once(DOKU_INC.'inc/JpegMeta.php'); 158567f9913dSAndreas Gohr $jpeg = new JpegMeta(mediaFN($src)); 1586b739ff0fSPierre Spring if($jpeg !== false) $cap = $jpeg->getTitle(); 15873dd5c225SAndreas Gohr if(!empty($cap)) { 1588b739ff0fSPierre Spring $title = $this->_xmlEntities($cap); 1589b739ff0fSPierre Spring } 1590b739ff0fSPierre Spring } 1591b739ff0fSPierre Spring if(!$render) { 1592b739ff0fSPierre Spring // if the picture is not supposed to be rendered 1593b739ff0fSPierre Spring // return the title of the picture 1594768be5a3SPhy if($title === null || $title === "") { 1595b739ff0fSPierre Spring // just show the sourcename 15968cbc5ee8SAndreas Gohr $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 1597b739ff0fSPierre Spring } 1598b739ff0fSPierre Spring return $title; 1599b739ff0fSPierre Spring } 16003fd0b676Sandi //add image tag 160164159a61SAndreas Gohr $ret .= '<img src="' . ml( 160264159a61SAndreas Gohr $src, 160364159a61SAndreas Gohr array( 160464159a61SAndreas Gohr 'w' => $width, 'h' => $height, 160564159a61SAndreas Gohr 'cache' => $cache, 160664159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 160764159a61SAndreas Gohr ) 160864159a61SAndreas Gohr ) . '"'; 16093fd0b676Sandi $ret .= ' class="media'.$align.'"'; 16104732b197SAndreas Gohr $ret .= ' loading="lazy"'; 16113fd0b676Sandi 1612b739ff0fSPierre Spring if($title) { 1613b739ff0fSPierre Spring $ret .= ' title="'.$title.'"'; 1614b739ff0fSPierre Spring $ret .= ' alt="'.$title.'"'; 16153fd0b676Sandi } else { 16163fd0b676Sandi $ret .= ' alt=""'; 16173fd0b676Sandi } 16183fd0b676Sandi 16193fd0b676Sandi if(!is_null($width)) 16203fd0b676Sandi $ret .= ' width="'.$this->_xmlEntities($width).'"'; 16213fd0b676Sandi 16223fd0b676Sandi if(!is_null($height)) 16233fd0b676Sandi $ret .= ' height="'.$this->_xmlEntities($height).'"'; 16243fd0b676Sandi 16253fd0b676Sandi $ret .= ' />'; 16263fd0b676Sandi 162717954bb5SAnika Henke } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 16282a2a2ba2SAnika Henke // first get the $title 1629a8dcb874Sbleistivt $title = !is_null($title) ? $title : false; 16302a2a2ba2SAnika Henke if(!$render) { 163117954bb5SAnika Henke // if the file is not supposed to be rendered 163217954bb5SAnika Henke // return the title of the file (just the sourcename if there is no title) 1633a8dcb874Sbleistivt return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src))); 16342a2a2ba2SAnika Henke } 16352a2a2ba2SAnika Henke 16362a2a2ba2SAnika Henke $att = array(); 16372a2a2ba2SAnika Henke $att['class'] = "media$align"; 163817954bb5SAnika Henke if($title) { 163917954bb5SAnika Henke $att['title'] = $title; 164017954bb5SAnika Henke } 16412a2a2ba2SAnika Henke 164217954bb5SAnika Henke if(media_supportedav($mime, 'video')) { 164317954bb5SAnika Henke //add video 164479e53fe5SAnika Henke $ret .= $this->_video($src, $width, $height, $att); 1645b44a5dceSAnika Henke } 164617954bb5SAnika Henke if(media_supportedav($mime, 'audio')) { 1647b44a5dceSAnika Henke //add audio 1648b44a5dceSAnika Henke $ret .= $this->_audio($src, $att); 164917954bb5SAnika Henke } 1650b44a5dceSAnika Henke 16513fd0b676Sandi } elseif($mime == 'application/x-shockwave-flash') { 16521c882ba8SAndreas Gohr if(!$render) { 16531c882ba8SAndreas Gohr // if the flash is not supposed to be rendered 16541c882ba8SAndreas Gohr // return the title of the flash 16551c882ba8SAndreas Gohr if(!$title) { 16561c882ba8SAndreas Gohr // just show the sourcename 16578cbc5ee8SAndreas Gohr $title = \dokuwiki\Utf8\PhpString::basename(noNS($src)); 16581c882ba8SAndreas Gohr } 165907bf32b2SAndreas Gohr return $this->_xmlEntities($title); 16601c882ba8SAndreas Gohr } 16611c882ba8SAndreas Gohr 166207bf32b2SAndreas Gohr $att = array(); 166307bf32b2SAndreas Gohr $att['class'] = "media$align"; 166407bf32b2SAndreas Gohr if($align == 'right') $att['align'] = 'right'; 166507bf32b2SAndreas Gohr if($align == 'left') $att['align'] = 'left'; 16663dd5c225SAndreas Gohr $ret .= html_flashobject( 16673dd5c225SAndreas Gohr ml($src, array('cache' => $cache), true, '&'), $width, $height, 166807bf32b2SAndreas Gohr array('quality' => 'high'), 166907bf32b2SAndreas Gohr null, 167007bf32b2SAndreas Gohr $att, 16713dd5c225SAndreas Gohr $this->_xmlEntities($title) 16723dd5c225SAndreas Gohr ); 16730f428d7dSAndreas Gohr } elseif($title) { 16743fd0b676Sandi // well at least we have a title to display 16753fd0b676Sandi $ret .= $this->_xmlEntities($title); 16763fd0b676Sandi } else { 16775291ca3aSAndreas Gohr // just show the sourcename 16788cbc5ee8SAndreas Gohr $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 16793fd0b676Sandi } 16803fd0b676Sandi 16813fd0b676Sandi return $ret; 16823fd0b676Sandi } 16833fd0b676Sandi 16843dd5c225SAndreas Gohr /** 16853dd5c225SAndreas Gohr * Escape string for output 16863dd5c225SAndreas Gohr * 16873dd5c225SAndreas Gohr * @param $string 16883dd5c225SAndreas Gohr * @return string 16893dd5c225SAndreas Gohr */ 1690de369923SAndreas Gohr public function _xmlEntities($string) { 1691f7711f2bSAndreas Gohr return hsc($string); 16920cecf9d5Sandi } 16930cecf9d5Sandi 1694de369923SAndreas Gohr 16950cecf9d5Sandi 1696af587fa8Sandi /** 16973fd0b676Sandi * Construct a title and handle images in titles 16983fd0b676Sandi * 16990b7c14c2Sandi * @author Harry Fuecks <hfuecks@gmail.com> 17003dd5c225SAndreas Gohr * @param string|array $title either string title or media array 17013dd5c225SAndreas Gohr * @param string $default default title if nothing else is found 17023dd5c225SAndreas Gohr * @param bool $isImage will be set to true if it's a media file 17033dd5c225SAndreas Gohr * @param null|string $id linked page id (used to extract title from first heading) 17043dd5c225SAndreas Gohr * @param string $linktype content|navigation 17053dd5c225SAndreas Gohr * @return string HTML of the title, might be full image tag or just escaped text 17063fd0b676Sandi */ 1707de369923SAndreas Gohr public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { 170844881bd0Shenning.noren $isImage = false; 170929657f9eSAndreas Gohr if(is_array($title)) { 171029657f9eSAndreas Gohr $isImage = true; 171129657f9eSAndreas Gohr return $this->_imageTitle($title); 171229657f9eSAndreas Gohr } elseif(is_null($title) || trim($title) == '') { 1713fe9ec250SChris Smith if(useHeading($linktype) && $id) { 171467c15eceSMichael Hamann $heading = p_get_first_heading($id); 1715f515db7fSAndreas Gohr if(!blank($heading)) { 1716433bef32Sandi return $this->_xmlEntities($heading); 1717bb0a59d4Sjan } 1718bb0a59d4Sjan } 1719433bef32Sandi return $this->_xmlEntities($default); 172068c26e6dSMichael Klier } else { 172168c26e6dSMichael Klier return $this->_xmlEntities($title); 17220cecf9d5Sandi } 17230cecf9d5Sandi } 17240cecf9d5Sandi 17250cecf9d5Sandi /** 17263dd5c225SAndreas Gohr * Returns HTML code for images used in link titles 17273fd0b676Sandi * 17283fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 1729e0c26282SGerrit Uitslag * @param array $img 17303dd5c225SAndreas Gohr * @return string HTML img tag or similar 17310cecf9d5Sandi */ 1732de369923SAndreas Gohr public function _imageTitle($img) { 1733d9baf1a7SKazutaka Miyasaka global $ID; 1734d9baf1a7SKazutaka Miyasaka 1735d9baf1a7SKazutaka Miyasaka // some fixes on $img['src'] 1736d9baf1a7SKazutaka Miyasaka // see internalmedia() and externalmedia() 17373dd5c225SAndreas Gohr list($img['src']) = explode('#', $img['src'], 2); 1738d9baf1a7SKazutaka Miyasaka if($img['type'] == 'internalmedia') { 17398c6be208SAndreas Gohr $img['src'] = (new MediaResolver($ID))->resolveId($img['src'], $this->date_at, true); 1740d9baf1a7SKazutaka Miyasaka } 1741d9baf1a7SKazutaka Miyasaka 17423dd5c225SAndreas Gohr return $this->_media( 17433dd5c225SAndreas Gohr $img['src'], 17444826ab45Sandi $img['title'], 17454826ab45Sandi $img['align'], 17464826ab45Sandi $img['width'], 17474826ab45Sandi $img['height'], 17483dd5c225SAndreas Gohr $img['cache'] 17493dd5c225SAndreas Gohr ); 17500cecf9d5Sandi } 1751b739ff0fSPierre Spring 1752b739ff0fSPierre Spring /** 17533dd5c225SAndreas Gohr * helperfunction to return a basic link to a media 17543dd5c225SAndreas Gohr * 17553dd5c225SAndreas Gohr * used in internalmedia() and externalmedia() 1756b739ff0fSPierre Spring * 1757b739ff0fSPierre Spring * @author Pierre Spring <pierre.spring@liip.ch> 17583dd5c225SAndreas Gohr * @param string $src media ID 17593dd5c225SAndreas Gohr * @param string $title descriptive text 17603dd5c225SAndreas Gohr * @param string $align left|center|right 17613dd5c225SAndreas Gohr * @param int $width width of media in pixel 17623dd5c225SAndreas Gohr * @param int $height height of media in pixel 17633dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 17643dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 17653dd5c225SAndreas Gohr * @return array associative array with link config 1766b739ff0fSPierre Spring */ 1767de369923SAndreas Gohr public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { 1768b739ff0fSPierre Spring global $conf; 1769b739ff0fSPierre Spring 1770b739ff0fSPierre Spring $link = array(); 1771b739ff0fSPierre Spring $link['class'] = 'media'; 1772b739ff0fSPierre Spring $link['style'] = ''; 1773b739ff0fSPierre Spring $link['pre'] = ''; 1774b739ff0fSPierre Spring $link['suf'] = ''; 1775b739ff0fSPierre Spring $link['more'] = ''; 1776b739ff0fSPierre Spring $link['target'] = $conf['target']['media']; 1777bc3d2252SAndreas Gohr if($conf['target']['media']) $link['rel'] = 'noopener'; 1778b739ff0fSPierre Spring $link['title'] = $this->_xmlEntities($src); 1779b739ff0fSPierre Spring $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1780b739ff0fSPierre Spring 1781b739ff0fSPierre Spring return $link; 1782b739ff0fSPierre Spring } 178391459163SAnika Henke 17842a2a2ba2SAnika Henke /** 17852a2a2ba2SAnika Henke * Embed video(s) in HTML 17862a2a2ba2SAnika Henke * 17872a2a2ba2SAnika Henke * @author Anika Henke <anika@selfthinker.org> 17880877a1f1SSchplurtz le Déboulonné * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 17892a2a2ba2SAnika Henke * 17902a2a2ba2SAnika Henke * @param string $src - ID of video to embed 17912a2a2ba2SAnika Henke * @param int $width - width of the video in pixels 17922a2a2ba2SAnika Henke * @param int $height - height of the video in pixels 17932a2a2ba2SAnika Henke * @param array $atts - additional attributes for the <video> tag 1794f50634f0SAnika Henke * @return string 17952a2a2ba2SAnika Henke */ 1796de369923SAndreas Gohr public function _video($src, $width, $height, $atts = null) { 17972a2a2ba2SAnika Henke // prepare width and height 17982a2a2ba2SAnika Henke if(is_null($atts)) $atts = array(); 17992a2a2ba2SAnika Henke $atts['width'] = (int) $width; 18002a2a2ba2SAnika Henke $atts['height'] = (int) $height; 18012a2a2ba2SAnika Henke if(!$atts['width']) $atts['width'] = 320; 18022a2a2ba2SAnika Henke if(!$atts['height']) $atts['height'] = 240; 18032a2a2ba2SAnika Henke 1804410ee62aSAnika Henke $posterUrl = ''; 1805410ee62aSAnika Henke $files = array(); 18060877a1f1SSchplurtz le Déboulonné $tracks = array(); 1807410ee62aSAnika Henke $isExternal = media_isexternal($src); 1808410ee62aSAnika Henke 1809410ee62aSAnika Henke if ($isExternal) { 1810410ee62aSAnika Henke // take direct source for external files 1811702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1812410ee62aSAnika Henke $files[$srcMime] = $src; 1813410ee62aSAnika Henke } else { 18143d7a9e0aSAnika Henke // prepare alternative formats 18153d7a9e0aSAnika Henke $extensions = array('webm', 'ogv', 'mp4'); 1816410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 181759bc3b48SGerrit Uitslag $poster = media_alternativefiles($src, array('jpg', 'png')); 18180877a1f1SSchplurtz le Déboulonné $tracks = media_trackfiles($src); 181999f943f6SAnika Henke if(!empty($poster)) { 18202d338eabSAndreas Gohr $posterUrl = ml(reset($poster), '', true, '&'); 182199f943f6SAnika Henke } 1822410ee62aSAnika Henke } 18232a2a2ba2SAnika Henke 1824f50634f0SAnika Henke $out = ''; 182579e53fe5SAnika Henke // open video tag 1826f50634f0SAnika Henke $out .= '<video '.buildAttributes($atts).' controls="controls"'; 18273641199aSAnika Henke if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; 1828f50634f0SAnika Henke $out .= '>'.NL; 18293641199aSAnika Henke $fallback = ''; 183079e53fe5SAnika Henke 183179e53fe5SAnika Henke // output source for each alternative video format 1832410ee62aSAnika Henke foreach($files as $mime => $file) { 1833410ee62aSAnika Henke if ($isExternal) { 1834410ee62aSAnika Henke $url = $file; 1835410ee62aSAnika Henke $linkType = 'externalmedia'; 1836410ee62aSAnika Henke } else { 18372d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1838410ee62aSAnika Henke $linkType = 'internalmedia'; 1839410ee62aSAnika Henke } 1840056bf31fSDamien Regad $title = !empty($atts['title']) 1841056bf31fSDamien Regad ? $atts['title'] 1842056bf31fSDamien Regad : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 18433d7a9e0aSAnika Henke 1844f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 184579e53fe5SAnika Henke // alternative content (just a link to the file) 184664159a61SAndreas Gohr $fallback .= $this->$linkType( 184764159a61SAndreas Gohr $file, 184864159a61SAndreas Gohr $title, 184964159a61SAndreas Gohr null, 185064159a61SAndreas Gohr null, 185164159a61SAndreas Gohr null, 185264159a61SAndreas Gohr $cache = null, 185364159a61SAndreas Gohr $linking = 'linkonly', 185464159a61SAndreas Gohr $return = true 185564159a61SAndreas Gohr ); 18563d7a9e0aSAnika Henke } 18572a2a2ba2SAnika Henke 18580877a1f1SSchplurtz le Déboulonné // output each track if any 18590877a1f1SSchplurtz le Déboulonné foreach( $tracks as $trackid => $info ) { 186023c61bbeSSchplurtz le Déboulonné list( $kind, $srclang ) = array_map( 'hsc', $info ); 186123c61bbeSSchplurtz le Déboulonné $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; 186223c61bbeSSchplurtz le Déboulonné $out .= "label=\"$srclang\" "; 18630877a1f1SSchplurtz le Déboulonné $out .= 'src="'.ml($trackid, '', true).'">'.NL; 18640877a1f1SSchplurtz le Déboulonné } 18650877a1f1SSchplurtz le Déboulonné 18662a2a2ba2SAnika Henke // finish 18673641199aSAnika Henke $out .= $fallback; 1868f50634f0SAnika Henke $out .= '</video>'.NL; 1869f50634f0SAnika Henke return $out; 18702a2a2ba2SAnika Henke } 18712a2a2ba2SAnika Henke 1872b44a5dceSAnika Henke /** 1873b44a5dceSAnika Henke * Embed audio in HTML 1874b44a5dceSAnika Henke * 1875b44a5dceSAnika Henke * @author Anika Henke <anika@selfthinker.org> 1876b44a5dceSAnika Henke * 1877b44a5dceSAnika Henke * @param string $src - ID of audio to embed 18786d4af72aSAnika Henke * @param array $atts - additional attributes for the <audio> tag 1879f50634f0SAnika Henke * @return string 1880b44a5dceSAnika Henke */ 1881de369923SAndreas Gohr public function _audio($src, $atts = array()) { 1882702e97d3SAnika Henke $files = array(); 1883410ee62aSAnika Henke $isExternal = media_isexternal($src); 1884b44a5dceSAnika Henke 1885410ee62aSAnika Henke if ($isExternal) { 1886410ee62aSAnika Henke // take direct source for external files 1887702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1888410ee62aSAnika Henke $files[$srcMime] = $src; 1889410ee62aSAnika Henke } else { 1890b44a5dceSAnika Henke // prepare alternative formats 1891b44a5dceSAnika Henke $extensions = array('ogg', 'mp3', 'wav'); 1892410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 1893410ee62aSAnika Henke } 1894b44a5dceSAnika Henke 1895f50634f0SAnika Henke $out = ''; 1896b44a5dceSAnika Henke // open audio tag 1897f50634f0SAnika Henke $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; 18983641199aSAnika Henke $fallback = ''; 1899b44a5dceSAnika Henke 1900b44a5dceSAnika Henke // output source for each alternative audio format 1901410ee62aSAnika Henke foreach($files as $mime => $file) { 1902410ee62aSAnika Henke if ($isExternal) { 1903410ee62aSAnika Henke $url = $file; 1904410ee62aSAnika Henke $linkType = 'externalmedia'; 1905410ee62aSAnika Henke } else { 19062d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1907410ee62aSAnika Henke $linkType = 'internalmedia'; 1908410ee62aSAnika Henke } 19098cbc5ee8SAndreas Gohr $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 1910b44a5dceSAnika Henke 1911f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1912b44a5dceSAnika Henke // alternative content (just a link to the file) 191364159a61SAndreas Gohr $fallback .= $this->$linkType( 191464159a61SAndreas Gohr $file, 191564159a61SAndreas Gohr $title, 191664159a61SAndreas Gohr null, 191764159a61SAndreas Gohr null, 191864159a61SAndreas Gohr null, 191964159a61SAndreas Gohr $cache = null, 192064159a61SAndreas Gohr $linking = 'linkonly', 192164159a61SAndreas Gohr $return = true 192264159a61SAndreas Gohr ); 1923b44a5dceSAnika Henke } 1924b44a5dceSAnika Henke 1925b44a5dceSAnika Henke // finish 19263641199aSAnika Henke $out .= $fallback; 1927f50634f0SAnika Henke $out .= '</audio>'.NL; 1928f50634f0SAnika Henke return $out; 1929b44a5dceSAnika Henke } 1930b44a5dceSAnika Henke 19315c2eed9aSlisps /** 193252dc5eadSlisps * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 19335c2eed9aSlisps * which returns an existing media revision less or equal to rev or date_at 19345c2eed9aSlisps * 19355c2eed9aSlisps * @author lisps 19365c2eed9aSlisps * @param string $media_id 19375c2eed9aSlisps * @access protected 19385c2eed9aSlisps * @return string revision ('' for current) 19395c2eed9aSlisps */ 1940de369923SAndreas Gohr protected function _getLastMediaRevisionAt($media_id) { 194152dc5eadSlisps if (!$this->date_at || media_isexternal($media_id)) return ''; 1942252acce3SSatoshi Sahara $changelog = new MediaChangeLog($media_id); 1943252acce3SSatoshi Sahara return $changelog->getLastRevisionAt($this->date_at); 19445c2eed9aSlisps } 19455c2eed9aSlisps 19463dd5c225SAndreas Gohr #endregion 19470cecf9d5Sandi} 19480cecf9d5Sandi 1949e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 1950