10cecf9d5Sandi<?php 20c3a5702SAndreas Gohr 30c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 40c3a5702SAndreas Gohr 5b625487dSandi/** 6b625487dSandi * Renderer for XHTML output 7b625487dSandi * 8b4f2363aSAndreas Gohr * This is DokuWiki's main renderer used to display page content in the wiki 9b4f2363aSAndreas Gohr * 10b625487dSandi * @author Harry Fuecks <hfuecks@gmail.com> 11b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 123dd5c225SAndreas Gohr * 130cecf9d5Sandi */ 14ac83b9d8Sandiclass Doku_Renderer_xhtml extends Doku_Renderer { 153dd5c225SAndreas Gohr /** @var array store the table of contents */ 163dd5c225SAndreas Gohr public $toc = array(); 170cecf9d5Sandi 183dd5c225SAndreas Gohr /** @var array A stack of section edit data */ 193dd5c225SAndreas Gohr protected $sectionedits = array(); 20de369923SAndreas Gohr 21de369923SAndreas Gohr /** @var string|int link pages and media against this revision */ 22de369923SAndreas Gohr public $date_at = ''; 23c5a8fd96SAndreas Gohr 243dd5c225SAndreas Gohr /** @var int last section edit id, used by startSectionEdit */ 253dd5c225SAndreas Gohr protected $lastsecid = 0; 260cecf9d5Sandi 2716ec3e37SAndreas Gohr /** @var array a list of footnotes, list starts at 1! */ 283dd5c225SAndreas Gohr protected $footnotes = array(); 297764a90aSandi 303dd5c225SAndreas Gohr /** @var int current section level */ 313dd5c225SAndreas Gohr protected $lastlevel = 0; 323dd5c225SAndreas Gohr /** @var array section node tracker */ 333dd5c225SAndreas Gohr protected $node = array(0, 0, 0, 0, 0); 343dd5c225SAndreas Gohr 353dd5c225SAndreas Gohr /** @var string temporary $doc store */ 363dd5c225SAndreas Gohr protected $store = ''; 373dd5c225SAndreas Gohr 383dd5c225SAndreas Gohr /** @var array global counter, for table classes etc. */ 393dd5c225SAndreas Gohr protected $_counter = array(); // 403dd5c225SAndreas Gohr 413dd5c225SAndreas Gohr /** @var int counts the code and file blocks, used to provide download links */ 423dd5c225SAndreas Gohr protected $_codeblock = 0; 433dd5c225SAndreas Gohr 443dd5c225SAndreas Gohr /** @var array list of allowed URL schemes */ 453dd5c225SAndreas Gohr protected $schemes = null; 46b5742cedSPierre Spring 4790df9a4dSAdrian Lang /** 4890df9a4dSAdrian Lang * Register a new edit section range 4990df9a4dSAdrian Lang * 5042ea7f44SGerrit Uitslag * @param int $start The byte position for the edit start 51ec57f119SLarsDW223 * @param array $data Associative array with section data: 52ec57f119SLarsDW223 * Key 'name': the section name/title 53ec57f119SLarsDW223 * Key 'target': the target for the section edit, 54ec57f119SLarsDW223 * e.g. 'section' or 'table' 55ec57f119SLarsDW223 * Key 'hid': header id 56ec57f119SLarsDW223 * Key 'codeblockOffset': actual code block index 57ec57f119SLarsDW223 * Key 'start': set in startSectionEdit(), 58ec57f119SLarsDW223 * do not set yourself 59ec57f119SLarsDW223 * Key 'range': calculated from 'start' and 60ec57f119SLarsDW223 * $key in finishSectionEdit(), 61ec57f119SLarsDW223 * do not set yourself 6290df9a4dSAdrian Lang * @return string A marker class for the starting HTML element 6342ea7f44SGerrit Uitslag * 6490df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 6590df9a4dSAdrian Lang */ 66ec57f119SLarsDW223 public function startSectionEdit($start, $data) { 67ec57f119SLarsDW223 if (!is_array($data)) { 68ac025fdfSAndreas Gohr msg( 69ac025fdfSAndreas Gohr sprintf( 70ac025fdfSAndreas Gohr 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.', 71ac025fdfSAndreas Gohr hsc((string) $data) 72ac025fdfSAndreas Gohr ), -1 73ac025fdfSAndreas Gohr ); 74ac025fdfSAndreas Gohr 75ac025fdfSAndreas Gohr // @deprecated 2018-04-14, backward compatibility 76ac025fdfSAndreas Gohr $args = func_get_args(); 77ac025fdfSAndreas Gohr $data = array(); 78ac025fdfSAndreas Gohr if(isset($args[1])) $data['target'] = $args[1]; 79ac025fdfSAndreas Gohr if(isset($args[2])) $data['name'] = $args[2]; 80ac025fdfSAndreas Gohr if(isset($args[3])) $data['hid'] = $args[3]; 81ec57f119SLarsDW223 } 82ec57f119SLarsDW223 $data['secid'] = ++$this->lastsecid; 83ec57f119SLarsDW223 $data['start'] = $start; 84ec57f119SLarsDW223 $this->sectionedits[] = $data; 85ec57f119SLarsDW223 return 'sectionedit'.$data['secid']; 8690df9a4dSAdrian Lang } 8790df9a4dSAdrian Lang 8890df9a4dSAdrian Lang /** 8990df9a4dSAdrian Lang * Finish an edit section range 9090df9a4dSAdrian Lang * 9142ea7f44SGerrit Uitslag * @param int $end The byte position for the edit end; null for the rest of the page 9242ea7f44SGerrit Uitslag * 9390df9a4dSAdrian Lang * @author Adrian Lang <lang@cosmocode.de> 9490df9a4dSAdrian Lang */ 952571786cSLarsDW223 public function finishSectionEdit($end = null, $hid = null) { 96ec57f119SLarsDW223 $data = array_pop($this->sectionedits); 97ec57f119SLarsDW223 if(!is_null($end) && $end <= $data['start']) { 9800c13053SAdrian Lang return; 9900c13053SAdrian Lang } 1002571786cSLarsDW223 if(!is_null($hid)) { 101ec57f119SLarsDW223 $data['hid'] .= $hid; 1022571786cSLarsDW223 } 103ec57f119SLarsDW223 $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end); 104ec57f119SLarsDW223 unset($data['start']); 105ada0d779SMichael Hamann $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->'; 10690df9a4dSAdrian Lang } 10790df9a4dSAdrian Lang 1083dd5c225SAndreas Gohr /** 1093dd5c225SAndreas Gohr * Returns the format produced by this renderer. 1103dd5c225SAndreas Gohr * 1113dd5c225SAndreas Gohr * @return string always 'xhtml' 1123dd5c225SAndreas Gohr */ 113de369923SAndreas Gohr public function getFormat() { 1145f70445dSAndreas Gohr return 'xhtml'; 1155f70445dSAndreas Gohr } 1165f70445dSAndreas Gohr 1173dd5c225SAndreas Gohr /** 1183dd5c225SAndreas Gohr * Initialize the document 1193dd5c225SAndreas Gohr */ 120de369923SAndreas Gohr public function document_start() { 121c5a8fd96SAndreas Gohr //reset some internals 122c5a8fd96SAndreas Gohr $this->toc = array(); 1230cecf9d5Sandi } 1240cecf9d5Sandi 1253dd5c225SAndreas Gohr /** 1263dd5c225SAndreas Gohr * Finalize the document 1273dd5c225SAndreas Gohr */ 128de369923SAndreas Gohr public function document_end() { 12990df9a4dSAdrian Lang // Finish open section edits. 13090df9a4dSAdrian Lang while(count($this->sectionedits) > 0) { 131ec57f119SLarsDW223 if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { 13290df9a4dSAdrian Lang // If there is only one section, do not write a section edit 13390df9a4dSAdrian Lang // marker. 13490df9a4dSAdrian Lang array_pop($this->sectionedits); 13590df9a4dSAdrian Lang } else { 136d9e36cbeSAdrian Lang $this->finishSectionEdit(); 13790df9a4dSAdrian Lang } 13890df9a4dSAdrian Lang } 13990df9a4dSAdrian Lang 1400cecf9d5Sandi if(count($this->footnotes) > 0) { 141a2d649c4Sandi $this->doc .= '<div class="footnotes">'.DOKU_LF; 142d74aace9Schris 14316ec3e37SAndreas Gohr foreach($this->footnotes as $id => $footnote) { 144d74aace9Schris // check its not a placeholder that indicates actual footnote text is elsewhere 145d74aace9Schris if(substr($footnote, 0, 5) != "@@FNT") { 146d74aace9Schris 147d74aace9Schris // open the footnote and set the anchor and backlink 148d74aace9Schris $this->doc .= '<div class="fn">'; 14916cc7ed7SAnika Henke $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">'; 15029bfcd16SAndreas Gohr $this->doc .= $id.')</a></sup> '.DOKU_LF; 151d74aace9Schris 152d74aace9Schris // get any other footnotes that use the same markup 153d74aace9Schris $alt = array_keys($this->footnotes, "@@FNT$id"); 154d74aace9Schris 155d74aace9Schris if(count($alt)) { 156d74aace9Schris foreach($alt as $ref) { 157d74aace9Schris // set anchor and backlink for the other footnotes 15816ec3e37SAndreas Gohr $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">'; 15916ec3e37SAndreas Gohr $this->doc .= ($ref).')</a></sup> '.DOKU_LF; 160d74aace9Schris } 161d74aace9Schris } 162d74aace9Schris 163d74aace9Schris // add footnote markup and close this footnote 164694afa06SAnika Henke $this->doc .= '<div class="content">'.$footnote.'</div>'; 165d74aace9Schris $this->doc .= '</div>'.DOKU_LF; 166d74aace9Schris } 1670cecf9d5Sandi } 168a2d649c4Sandi $this->doc .= '</div>'.DOKU_LF; 1690cecf9d5Sandi } 170c5a8fd96SAndreas Gohr 171b8595a66SAndreas Gohr // Prepare the TOC 172851f2e89SAnika Henke global $conf; 17364159a61SAndreas Gohr if( 17464159a61SAndreas Gohr $this->info['toc'] && 17564159a61SAndreas Gohr is_array($this->toc) && 17664159a61SAndreas Gohr $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] 17764159a61SAndreas Gohr ) { 178b8595a66SAndreas Gohr global $TOC; 179b8595a66SAndreas Gohr $TOC = $this->toc; 1800cecf9d5Sandi } 1813e55d035SAndreas Gohr 1823e55d035SAndreas Gohr // make sure there are no empty paragraphs 18327918226Schris $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc); 184e41c4da9SAndreas Gohr } 1850cecf9d5Sandi 1863dd5c225SAndreas Gohr /** 1873dd5c225SAndreas Gohr * Add an item to the TOC 1883dd5c225SAndreas Gohr * 1893dd5c225SAndreas Gohr * @param string $id the hash link 1903dd5c225SAndreas Gohr * @param string $text the text to display 1913dd5c225SAndreas Gohr * @param int $level the nesting level 1923dd5c225SAndreas Gohr */ 193de369923SAndreas Gohr public function toc_additem($id, $text, $level) { 194af587fa8Sandi global $conf; 195af587fa8Sandi 196c5a8fd96SAndreas Gohr //handle TOC 197c5a8fd96SAndreas Gohr if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { 1987d91652aSAndreas Gohr $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1); 199c5a8fd96SAndreas Gohr } 200e7856beaSchris } 201e7856beaSchris 2023dd5c225SAndreas Gohr /** 2033dd5c225SAndreas Gohr * Render a heading 2043dd5c225SAndreas Gohr * 2053dd5c225SAndreas Gohr * @param string $text the text to display 2063dd5c225SAndreas Gohr * @param int $level header level 2073dd5c225SAndreas Gohr * @param int $pos byte position in the original source 2083dd5c225SAndreas Gohr */ 209de369923SAndreas Gohr public function header($text, $level, $pos) { 21090df9a4dSAdrian Lang global $conf; 21190df9a4dSAdrian Lang 212f515db7fSAndreas Gohr if(blank($text)) return; //skip empty headlines 213e7856beaSchris 214e7856beaSchris $hid = $this->_headerToLink($text, true); 215e7856beaSchris 216e7856beaSchris //only add items within configured levels 217e7856beaSchris $this->toc_additem($hid, $text, $level); 218c5a8fd96SAndreas Gohr 21991459163SAnika Henke // adjust $node to reflect hierarchy of levels 22091459163SAnika Henke $this->node[$level - 1]++; 22191459163SAnika Henke if($level < $this->lastlevel) { 22291459163SAnika Henke for($i = 0; $i < $this->lastlevel - $level; $i++) { 22391459163SAnika Henke $this->node[$this->lastlevel - $i - 1] = 0; 22491459163SAnika Henke } 22591459163SAnika Henke } 22691459163SAnika Henke $this->lastlevel = $level; 22791459163SAnika Henke 22890df9a4dSAdrian Lang if($level <= $conf['maxseclevel'] && 22990df9a4dSAdrian Lang count($this->sectionedits) > 0 && 230ec57f119SLarsDW223 $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section' 2313dd5c225SAndreas Gohr ) { 2326c1f778cSAdrian Lang $this->finishSectionEdit($pos - 1); 23390df9a4dSAdrian Lang } 23490df9a4dSAdrian Lang 235c5a8fd96SAndreas Gohr // write the header 23690df9a4dSAdrian Lang $this->doc .= DOKU_LF.'<h'.$level; 23790df9a4dSAdrian Lang if($level <= $conf['maxseclevel']) { 238ec57f119SLarsDW223 $data = array(); 239ec57f119SLarsDW223 $data['target'] = 'section'; 240ec57f119SLarsDW223 $data['name'] = $text; 241ec57f119SLarsDW223 $data['hid'] = $hid; 242ec57f119SLarsDW223 $data['codeblockOffset'] = $this->_codeblock; 243ec57f119SLarsDW223 $this->doc .= ' class="'.$this->startSectionEdit($pos, $data).'"'; 24490df9a4dSAdrian Lang } 24516cc7ed7SAnika Henke $this->doc .= ' id="'.$hid.'">'; 246a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 24716cc7ed7SAnika Henke $this->doc .= "</h$level>".DOKU_LF; 2480cecf9d5Sandi } 2490cecf9d5Sandi 2503dd5c225SAndreas Gohr /** 2513dd5c225SAndreas Gohr * Open a new section 2523dd5c225SAndreas Gohr * 2533dd5c225SAndreas Gohr * @param int $level section level (as determined by the previous header) 2543dd5c225SAndreas Gohr */ 255de369923SAndreas Gohr public function section_open($level) { 2569864e7b1SAdrian Lang $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; 2570cecf9d5Sandi } 2580cecf9d5Sandi 2593dd5c225SAndreas Gohr /** 2603dd5c225SAndreas Gohr * Close the current section 2613dd5c225SAndreas Gohr */ 262de369923SAndreas Gohr public function section_close() { 263a2d649c4Sandi $this->doc .= DOKU_LF.'</div>'.DOKU_LF; 2640cecf9d5Sandi } 2650cecf9d5Sandi 2663dd5c225SAndreas Gohr /** 2673dd5c225SAndreas Gohr * Render plain text data 2683dd5c225SAndreas Gohr * 2693dd5c225SAndreas Gohr * @param $text 2703dd5c225SAndreas Gohr */ 271de369923SAndreas Gohr public function cdata($text) { 272a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 2730cecf9d5Sandi } 2740cecf9d5Sandi 2753dd5c225SAndreas Gohr /** 2763dd5c225SAndreas Gohr * Open a paragraph 2773dd5c225SAndreas Gohr */ 278de369923SAndreas Gohr public function p_open() { 27959869a4bSAnika Henke $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 2800cecf9d5Sandi } 2810cecf9d5Sandi 2823dd5c225SAndreas Gohr /** 2833dd5c225SAndreas Gohr * Close a paragraph 2843dd5c225SAndreas Gohr */ 285de369923SAndreas Gohr public function p_close() { 28659869a4bSAnika Henke $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 2870cecf9d5Sandi } 2880cecf9d5Sandi 2893dd5c225SAndreas Gohr /** 2903dd5c225SAndreas Gohr * Create a line break 2913dd5c225SAndreas Gohr */ 292de369923SAndreas Gohr public function linebreak() { 293a2d649c4Sandi $this->doc .= '<br/>'.DOKU_LF; 2940cecf9d5Sandi } 2950cecf9d5Sandi 2963dd5c225SAndreas Gohr /** 2973dd5c225SAndreas Gohr * Create a horizontal line 2983dd5c225SAndreas Gohr */ 299de369923SAndreas Gohr public function hr() { 3004beabca9SAnika Henke $this->doc .= '<hr />'.DOKU_LF; 3010cecf9d5Sandi } 3020cecf9d5Sandi 3033dd5c225SAndreas Gohr /** 3043dd5c225SAndreas Gohr * Start strong (bold) formatting 3053dd5c225SAndreas Gohr */ 306de369923SAndreas Gohr public function strong_open() { 307a2d649c4Sandi $this->doc .= '<strong>'; 3080cecf9d5Sandi } 3090cecf9d5Sandi 3103dd5c225SAndreas Gohr /** 3113dd5c225SAndreas Gohr * Stop strong (bold) formatting 3123dd5c225SAndreas Gohr */ 313de369923SAndreas Gohr public function strong_close() { 314a2d649c4Sandi $this->doc .= '</strong>'; 3150cecf9d5Sandi } 3160cecf9d5Sandi 3173dd5c225SAndreas Gohr /** 3183dd5c225SAndreas Gohr * Start emphasis (italics) formatting 3193dd5c225SAndreas Gohr */ 320de369923SAndreas Gohr public function emphasis_open() { 321a2d649c4Sandi $this->doc .= '<em>'; 3220cecf9d5Sandi } 3230cecf9d5Sandi 3243dd5c225SAndreas Gohr /** 3253dd5c225SAndreas Gohr * Stop emphasis (italics) formatting 3263dd5c225SAndreas Gohr */ 327de369923SAndreas Gohr public function emphasis_close() { 328a2d649c4Sandi $this->doc .= '</em>'; 3290cecf9d5Sandi } 3300cecf9d5Sandi 3313dd5c225SAndreas Gohr /** 3323dd5c225SAndreas Gohr * Start underline formatting 3333dd5c225SAndreas Gohr */ 334de369923SAndreas Gohr public function underline_open() { 33502e51121SAnika Henke $this->doc .= '<em class="u">'; 3360cecf9d5Sandi } 3370cecf9d5Sandi 3383dd5c225SAndreas Gohr /** 3393dd5c225SAndreas Gohr * Stop underline formatting 3403dd5c225SAndreas Gohr */ 341de369923SAndreas Gohr public function underline_close() { 34202e51121SAnika Henke $this->doc .= '</em>'; 3430cecf9d5Sandi } 3440cecf9d5Sandi 3453dd5c225SAndreas Gohr /** 3463dd5c225SAndreas Gohr * Start monospace formatting 3473dd5c225SAndreas Gohr */ 348de369923SAndreas Gohr public function monospace_open() { 349a2d649c4Sandi $this->doc .= '<code>'; 3500cecf9d5Sandi } 3510cecf9d5Sandi 3523dd5c225SAndreas Gohr /** 3533dd5c225SAndreas Gohr * Stop monospace formatting 3543dd5c225SAndreas Gohr */ 355de369923SAndreas Gohr public function monospace_close() { 356a2d649c4Sandi $this->doc .= '</code>'; 3570cecf9d5Sandi } 3580cecf9d5Sandi 3593dd5c225SAndreas Gohr /** 3603dd5c225SAndreas Gohr * Start a subscript 3613dd5c225SAndreas Gohr */ 362de369923SAndreas Gohr public function subscript_open() { 363a2d649c4Sandi $this->doc .= '<sub>'; 3640cecf9d5Sandi } 3650cecf9d5Sandi 3663dd5c225SAndreas Gohr /** 3673dd5c225SAndreas Gohr * Stop a subscript 3683dd5c225SAndreas Gohr */ 369de369923SAndreas Gohr public function subscript_close() { 370a2d649c4Sandi $this->doc .= '</sub>'; 3710cecf9d5Sandi } 3720cecf9d5Sandi 3733dd5c225SAndreas Gohr /** 3743dd5c225SAndreas Gohr * Start a superscript 3753dd5c225SAndreas Gohr */ 376de369923SAndreas Gohr public function superscript_open() { 377a2d649c4Sandi $this->doc .= '<sup>'; 3780cecf9d5Sandi } 3790cecf9d5Sandi 3803dd5c225SAndreas Gohr /** 3813dd5c225SAndreas Gohr * Stop a superscript 3823dd5c225SAndreas Gohr */ 383de369923SAndreas Gohr public function superscript_close() { 384a2d649c4Sandi $this->doc .= '</sup>'; 3850cecf9d5Sandi } 3860cecf9d5Sandi 3873dd5c225SAndreas Gohr /** 3883dd5c225SAndreas Gohr * Start deleted (strike-through) formatting 3893dd5c225SAndreas Gohr */ 390de369923SAndreas Gohr public function deleted_open() { 391a2d649c4Sandi $this->doc .= '<del>'; 3920cecf9d5Sandi } 3930cecf9d5Sandi 3943dd5c225SAndreas Gohr /** 3953dd5c225SAndreas Gohr * Stop deleted (strike-through) formatting 3963dd5c225SAndreas Gohr */ 397de369923SAndreas Gohr public function deleted_close() { 398a2d649c4Sandi $this->doc .= '</del>'; 3990cecf9d5Sandi } 4000cecf9d5Sandi 4013fd0b676Sandi /** 4023fd0b676Sandi * Callback for footnote start syntax 4033fd0b676Sandi * 4043fd0b676Sandi * All following content will go to the footnote instead of 405d74aace9Schris * the document. To achieve this the previous rendered content 4063fd0b676Sandi * is moved to $store and $doc is cleared 4073fd0b676Sandi * 4083fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 4093fd0b676Sandi */ 410de369923SAndreas Gohr public function footnote_open() { 4117764a90aSandi 4127764a90aSandi // move current content to store and record footnote 4137764a90aSandi $this->store = $this->doc; 4147764a90aSandi $this->doc = ''; 4150cecf9d5Sandi } 4160cecf9d5Sandi 4173fd0b676Sandi /** 4183fd0b676Sandi * Callback for footnote end syntax 4193fd0b676Sandi * 4203fd0b676Sandi * All rendered content is moved to the $footnotes array and the old 4213fd0b676Sandi * content is restored from $store again 4223fd0b676Sandi * 4233fd0b676Sandi * @author Andreas Gohr 4243fd0b676Sandi */ 425de369923SAndreas Gohr public function footnote_close() { 42616ec3e37SAndreas Gohr /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ 42716ec3e37SAndreas Gohr static $fnid = 0; 42816ec3e37SAndreas Gohr // assign new footnote id (we start at 1) 42916ec3e37SAndreas Gohr $fnid++; 4307764a90aSandi 431d74aace9Schris // recover footnote into the stack and restore old content 432d74aace9Schris $footnote = $this->doc; 4337764a90aSandi $this->doc = $this->store; 4347764a90aSandi $this->store = ''; 435d74aace9Schris 436d74aace9Schris // check to see if this footnote has been seen before 437d74aace9Schris $i = array_search($footnote, $this->footnotes); 438d74aace9Schris 439d74aace9Schris if($i === false) { 440d74aace9Schris // its a new footnote, add it to the $footnotes array 44116ec3e37SAndreas Gohr $this->footnotes[$fnid] = $footnote; 442d74aace9Schris } else { 44316ec3e37SAndreas Gohr // seen this one before, save a placeholder 44416ec3e37SAndreas Gohr $this->footnotes[$fnid] = "@@FNT".($i); 445d74aace9Schris } 446d74aace9Schris 4476b379cbfSAndreas Gohr // output the footnote reference and link 44816ec3e37SAndreas Gohr $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>'; 4490cecf9d5Sandi } 4500cecf9d5Sandi 4513dd5c225SAndreas Gohr /** 4523dd5c225SAndreas Gohr * Open an unordered list 4530c4c0281SGerrit Uitslag * 4547d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4553dd5c225SAndreas Gohr */ 456de369923SAndreas Gohr public function listu_open($classes = null) { 4570c4c0281SGerrit Uitslag $class = ''; 4580c4c0281SGerrit Uitslag if($classes !== null) { 4592e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4600c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4610c4c0281SGerrit Uitslag } 4620c4c0281SGerrit Uitslag $this->doc .= "<ul$class>".DOKU_LF; 4630cecf9d5Sandi } 4640cecf9d5Sandi 4653dd5c225SAndreas Gohr /** 4663dd5c225SAndreas Gohr * Close an unordered list 4673dd5c225SAndreas Gohr */ 468de369923SAndreas Gohr public function listu_close() { 469a2d649c4Sandi $this->doc .= '</ul>'.DOKU_LF; 4700cecf9d5Sandi } 4710cecf9d5Sandi 4723dd5c225SAndreas Gohr /** 4733dd5c225SAndreas Gohr * Open an ordered list 4740c4c0281SGerrit Uitslag * 4757d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 4763dd5c225SAndreas Gohr */ 477de369923SAndreas Gohr public function listo_open($classes = null) { 4780c4c0281SGerrit Uitslag $class = ''; 4790c4c0281SGerrit Uitslag if($classes !== null) { 4802e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 4810c4c0281SGerrit Uitslag $class = " class=\"$classes\""; 4820c4c0281SGerrit Uitslag } 4830c4c0281SGerrit Uitslag $this->doc .= "<ol$class>".DOKU_LF; 4840cecf9d5Sandi } 4850cecf9d5Sandi 4863dd5c225SAndreas Gohr /** 4873dd5c225SAndreas Gohr * Close an ordered list 4883dd5c225SAndreas Gohr */ 489de369923SAndreas Gohr public function listo_close() { 490a2d649c4Sandi $this->doc .= '</ol>'.DOKU_LF; 4910cecf9d5Sandi } 4920cecf9d5Sandi 4933dd5c225SAndreas Gohr /** 4943dd5c225SAndreas Gohr * Open a list item 4953dd5c225SAndreas Gohr * 4963dd5c225SAndreas Gohr * @param int $level the nesting level 497e3a24861SChristopher Smith * @param bool $node true when a node; false when a leaf 4983dd5c225SAndreas Gohr */ 499de369923SAndreas Gohr public function listitem_open($level, $node=false) { 500e3a24861SChristopher Smith $branching = $node ? ' node' : ''; 501e3a24861SChristopher Smith $this->doc .= '<li class="level'.$level.$branching.'">'; 5020cecf9d5Sandi } 5030cecf9d5Sandi 5043dd5c225SAndreas Gohr /** 5053dd5c225SAndreas Gohr * Close a list item 5063dd5c225SAndreas Gohr */ 507de369923SAndreas Gohr public function listitem_close() { 508a2d649c4Sandi $this->doc .= '</li>'.DOKU_LF; 5090cecf9d5Sandi } 5100cecf9d5Sandi 5113dd5c225SAndreas Gohr /** 5123dd5c225SAndreas Gohr * Start the content of a list item 5133dd5c225SAndreas Gohr */ 514de369923SAndreas Gohr public function listcontent_open() { 51590db23d7Schris $this->doc .= '<div class="li">'; 5160cecf9d5Sandi } 5170cecf9d5Sandi 5183dd5c225SAndreas Gohr /** 5193dd5c225SAndreas Gohr * Stop the content of a list item 5203dd5c225SAndreas Gohr */ 521de369923SAndreas Gohr public function listcontent_close() { 52259869a4bSAnika Henke $this->doc .= '</div>'.DOKU_LF; 5230cecf9d5Sandi } 5240cecf9d5Sandi 5253dd5c225SAndreas Gohr /** 5263dd5c225SAndreas Gohr * Output unformatted $text 5273dd5c225SAndreas Gohr * 5283dd5c225SAndreas Gohr * Defaults to $this->cdata() 5293dd5c225SAndreas Gohr * 5303dd5c225SAndreas Gohr * @param string $text 5313dd5c225SAndreas Gohr */ 532de369923SAndreas Gohr public function unformatted($text) { 533a2d649c4Sandi $this->doc .= $this->_xmlEntities($text); 5340cecf9d5Sandi } 5350cecf9d5Sandi 5360cecf9d5Sandi /** 5373fd0b676Sandi * Execute PHP code if allowed 5383fd0b676Sandi * 539d9764001SMichael Hamann * @param string $text PHP code that is either executed or printed 5405d568b99SChris Smith * @param string $wrapper html element to wrap result if $conf['phpok'] is okff 5415d568b99SChris Smith * 5423fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 5430cecf9d5Sandi */ 544de369923SAndreas Gohr public function php($text, $wrapper = 'code') { 54535a56260SChris Smith global $conf; 54635a56260SChris Smith 547d86d5af0SChris Smith if($conf['phpok']) { 548bad0b545Sandi ob_start(); 5494de671bcSandi eval($text); 5503fd0b676Sandi $this->doc .= ob_get_contents(); 551bad0b545Sandi ob_end_clean(); 552d86d5af0SChris Smith } else { 5535d568b99SChris Smith $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 554d86d5af0SChris Smith } 5550cecf9d5Sandi } 5560cecf9d5Sandi 5573dd5c225SAndreas Gohr /** 5583dd5c225SAndreas Gohr * Output block level PHP code 5593dd5c225SAndreas Gohr * 5603dd5c225SAndreas Gohr * If $conf['phpok'] is true this should evaluate the given code and append the result 5613dd5c225SAndreas Gohr * to $doc 5623dd5c225SAndreas Gohr * 5633dd5c225SAndreas Gohr * @param string $text The PHP code 5643dd5c225SAndreas Gohr */ 565de369923SAndreas Gohr public function phpblock($text) { 5665d568b99SChris Smith $this->php($text, 'pre'); 56707f89c3cSAnika Henke } 56807f89c3cSAnika Henke 5690cecf9d5Sandi /** 5703fd0b676Sandi * Insert HTML if allowed 5713fd0b676Sandi * 572d9764001SMichael Hamann * @param string $text html text 5735d568b99SChris Smith * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff 5745d568b99SChris Smith * 5753fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 5760cecf9d5Sandi */ 577de369923SAndreas Gohr public function html($text, $wrapper = 'code') { 57835a56260SChris Smith global $conf; 57935a56260SChris Smith 580d86d5af0SChris Smith if($conf['htmlok']) { 581a2d649c4Sandi $this->doc .= $text; 582d86d5af0SChris Smith } else { 5835d568b99SChris Smith $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 584d86d5af0SChris Smith } 5854de671bcSandi } 5860cecf9d5Sandi 5873dd5c225SAndreas Gohr /** 5883dd5c225SAndreas Gohr * Output raw block-level HTML 5893dd5c225SAndreas Gohr * 5903dd5c225SAndreas Gohr * If $conf['htmlok'] is true this should add the code as is to $doc 5913dd5c225SAndreas Gohr * 5923dd5c225SAndreas Gohr * @param string $text The HTML 5933dd5c225SAndreas Gohr */ 594de369923SAndreas Gohr public function htmlblock($text) { 5955d568b99SChris Smith $this->html($text, 'pre'); 59607f89c3cSAnika Henke } 59707f89c3cSAnika Henke 5983dd5c225SAndreas Gohr /** 5993dd5c225SAndreas Gohr * Start a block quote 6003dd5c225SAndreas Gohr */ 601de369923SAndreas Gohr public function quote_open() { 60296331712SAnika Henke $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 6030cecf9d5Sandi } 6040cecf9d5Sandi 6053dd5c225SAndreas Gohr /** 6063dd5c225SAndreas Gohr * Stop a block quote 6073dd5c225SAndreas Gohr */ 608de369923SAndreas Gohr public function quote_close() { 60996331712SAnika Henke $this->doc .= '</div></blockquote>'.DOKU_LF; 6100cecf9d5Sandi } 6110cecf9d5Sandi 6123dd5c225SAndreas Gohr /** 6133dd5c225SAndreas Gohr * Output preformatted text 6143dd5c225SAndreas Gohr * 6153dd5c225SAndreas Gohr * @param string $text 6163dd5c225SAndreas Gohr */ 617de369923SAndreas Gohr public function preformatted($text) { 618c9250713SAnika Henke $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; 6193d491f75SAndreas Gohr } 6203d491f75SAndreas Gohr 6213dd5c225SAndreas Gohr /** 6223dd5c225SAndreas Gohr * Display text as file content, optionally syntax highlighted 6233dd5c225SAndreas Gohr * 6243dd5c225SAndreas Gohr * @param string $text text to show 6253dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6263dd5c225SAndreas Gohr * @param string $filename file path label 627e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6283dd5c225SAndreas Gohr */ 629de369923SAndreas Gohr public function file($text, $language = null, $filename = null, $options=null) { 630e2d88156SLarsDW223 $this->_highlight('file', $text, $language, $filename, $options); 6313d491f75SAndreas Gohr } 6323d491f75SAndreas Gohr 6333dd5c225SAndreas Gohr /** 6343dd5c225SAndreas Gohr * Display text as code content, optionally syntax highlighted 6353dd5c225SAndreas Gohr * 6363dd5c225SAndreas Gohr * @param string $text text to show 6373dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6383dd5c225SAndreas Gohr * @param string $filename file path label 639e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6403dd5c225SAndreas Gohr */ 641de369923SAndreas Gohr public function code($text, $language = null, $filename = null, $options=null) { 642e2d88156SLarsDW223 $this->_highlight('code', $text, $language, $filename, $options); 6433d491f75SAndreas Gohr } 6443d491f75SAndreas Gohr 6450cecf9d5Sandi /** 6463d491f75SAndreas Gohr * Use GeSHi to highlight language syntax in code and file blocks 6473fd0b676Sandi * 6483fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 6493dd5c225SAndreas Gohr * @param string $type code|file 6503dd5c225SAndreas Gohr * @param string $text text to show 6513dd5c225SAndreas Gohr * @param string $language programming language to use for syntax highlighting 6523dd5c225SAndreas Gohr * @param string $filename file path label 653e2d88156SLarsDW223 * @param array $options assoziative array with additional geshi options 6540cecf9d5Sandi */ 655de369923SAndreas Gohr public function _highlight($type, $text, $language = null, $filename = null, $options = null) { 6563d491f75SAndreas Gohr global $ID; 6573d491f75SAndreas Gohr global $lang; 658ec57f119SLarsDW223 global $INPUT; 6593d491f75SAndreas Gohr 66056bd9509SPhy $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language); 66156bd9509SPhy 6623d491f75SAndreas Gohr if($filename) { 663190c56e8SAndreas Gohr // add icon 66427bf7924STom N Harris list($ext) = mimetype($filename, false); 665190c56e8SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 666190c56e8SAndreas Gohr $class = 'mediafile mf_'.$class; 667190c56e8SAndreas Gohr 668ec57f119SLarsDW223 $offset = 0; 669ec57f119SLarsDW223 if ($INPUT->has('codeblockOffset')) { 670ec57f119SLarsDW223 $offset = $INPUT->str('codeblockOffset'); 671ec57f119SLarsDW223 } 6723d491f75SAndreas Gohr $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 67364159a61SAndreas Gohr $this->doc .= '<dt><a href="' . 67464159a61SAndreas Gohr exportlink( 67564159a61SAndreas Gohr $ID, 67664159a61SAndreas Gohr 'code', 67764159a61SAndreas Gohr array('codeblock' => $offset + $this->_codeblock) 67864159a61SAndreas Gohr ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; 6793d491f75SAndreas Gohr $this->doc .= hsc($filename); 6803d491f75SAndreas Gohr $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 6813d491f75SAndreas Gohr } 6820cecf9d5Sandi 6832401f18dSSyntaxseed if($text[0] == "\n") { 684d43aac1cSGina Haeussge $text = substr($text, 1); 685d43aac1cSGina Haeussge } 686d43aac1cSGina Haeussge if(substr($text, -1) == "\n") { 687d43aac1cSGina Haeussge $text = substr($text, 0, -1); 688d43aac1cSGina Haeussge } 689d43aac1cSGina Haeussge 690a056e285SPhy if(empty($language)) { // empty is faster than is_null and can prevent '' string 6913d491f75SAndreas Gohr $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF; 6920cecf9d5Sandi } else { 6933d491f75SAndreas Gohr $class = 'code'; //we always need the code class to make the syntax highlighting apply 6943d491f75SAndreas Gohr if($type != 'code') $class .= ' '.$type; 6953d491f75SAndreas Gohr 69664159a61SAndreas Gohr $this->doc .= "<pre class=\"$class $language\">" . 69764159a61SAndreas Gohr p_xhtml_cached_geshi($text, $language, '', $options) . 69864159a61SAndreas Gohr '</pre>' . DOKU_LF; 6990cecf9d5Sandi } 7003d491f75SAndreas Gohr 7013d491f75SAndreas Gohr if($filename) { 7023d491f75SAndreas Gohr $this->doc .= '</dd></dl>'.DOKU_LF; 7033d491f75SAndreas Gohr } 7043d491f75SAndreas Gohr 7053d491f75SAndreas Gohr $this->_codeblock++; 7060cecf9d5Sandi } 7070cecf9d5Sandi 7083dd5c225SAndreas Gohr /** 7093dd5c225SAndreas Gohr * Format an acronym 7103dd5c225SAndreas Gohr * 7113dd5c225SAndreas Gohr * Uses $this->acronyms 7123dd5c225SAndreas Gohr * 7133dd5c225SAndreas Gohr * @param string $acronym 7143dd5c225SAndreas Gohr */ 715de369923SAndreas Gohr public function acronym($acronym) { 7160cecf9d5Sandi 7170cecf9d5Sandi if(array_key_exists($acronym, $this->acronyms)) { 7180cecf9d5Sandi 719433bef32Sandi $title = $this->_xmlEntities($this->acronyms[$acronym]); 7200cecf9d5Sandi 721940db3a3SAnika Henke $this->doc .= '<abbr title="'.$title 722940db3a3SAnika Henke .'">'.$this->_xmlEntities($acronym).'</abbr>'; 7230cecf9d5Sandi 7240cecf9d5Sandi } else { 725a2d649c4Sandi $this->doc .= $this->_xmlEntities($acronym); 7260cecf9d5Sandi } 7270cecf9d5Sandi } 7280cecf9d5Sandi 7293dd5c225SAndreas Gohr /** 7303dd5c225SAndreas Gohr * Format a smiley 7313dd5c225SAndreas Gohr * 7323dd5c225SAndreas Gohr * Uses $this->smiley 7333dd5c225SAndreas Gohr * 7343dd5c225SAndreas Gohr * @param string $smiley 7353dd5c225SAndreas Gohr */ 736de369923SAndreas Gohr public function smiley($smiley) { 737b09504a9SAndreas Gohr if (isset($this->smileys[$smiley])) { 738f62ea8a1Sandi $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] . 739b09504a9SAndreas Gohr '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />'; 7400cecf9d5Sandi } else { 741a2d649c4Sandi $this->doc .= $this->_xmlEntities($smiley); 7420cecf9d5Sandi } 7430cecf9d5Sandi } 7440cecf9d5Sandi 7453dd5c225SAndreas Gohr /** 7463dd5c225SAndreas Gohr * Format an entity 7473dd5c225SAndreas Gohr * 7483dd5c225SAndreas Gohr * Entities are basically small text replacements 7493dd5c225SAndreas Gohr * 7503dd5c225SAndreas Gohr * Uses $this->entities 7513dd5c225SAndreas Gohr * 7523dd5c225SAndreas Gohr * @param string $entity 7534de671bcSandi */ 754de369923SAndreas Gohr public function entity($entity) { 7550cecf9d5Sandi if(array_key_exists($entity, $this->entities)) { 756a2d649c4Sandi $this->doc .= $this->entities[$entity]; 7570cecf9d5Sandi } else { 758a2d649c4Sandi $this->doc .= $this->_xmlEntities($entity); 7590cecf9d5Sandi } 7600cecf9d5Sandi } 7610cecf9d5Sandi 7623dd5c225SAndreas Gohr /** 7633dd5c225SAndreas Gohr * Typographically format a multiply sign 7643dd5c225SAndreas Gohr * 7653dd5c225SAndreas Gohr * Example: ($x=640, $y=480) should result in "640×480" 7663dd5c225SAndreas Gohr * 7673dd5c225SAndreas Gohr * @param string|int $x first value 7683dd5c225SAndreas Gohr * @param string|int $y second value 7693dd5c225SAndreas Gohr */ 770de369923SAndreas Gohr public function multiplyentity($x, $y) { 771a2d649c4Sandi $this->doc .= "$x×$y"; 7720cecf9d5Sandi } 7730cecf9d5Sandi 7743dd5c225SAndreas Gohr /** 7753dd5c225SAndreas Gohr * Render an opening single quote char (language specific) 7763dd5c225SAndreas Gohr */ 777de369923SAndreas Gohr public function singlequoteopening() { 77871b40da2SAnika Henke global $lang; 77971b40da2SAnika Henke $this->doc .= $lang['singlequoteopening']; 7800cecf9d5Sandi } 7810cecf9d5Sandi 7823dd5c225SAndreas Gohr /** 7833dd5c225SAndreas Gohr * Render a closing single quote char (language specific) 7843dd5c225SAndreas Gohr */ 785de369923SAndreas Gohr public function singlequoteclosing() { 78671b40da2SAnika Henke global $lang; 78771b40da2SAnika Henke $this->doc .= $lang['singlequoteclosing']; 7880cecf9d5Sandi } 7890cecf9d5Sandi 7903dd5c225SAndreas Gohr /** 7913dd5c225SAndreas Gohr * Render an apostrophe char (language specific) 7923dd5c225SAndreas Gohr */ 793de369923SAndreas Gohr public function apostrophe() { 79457d757d1SAndreas Gohr global $lang; 795a8bd192aSAndreas Gohr $this->doc .= $lang['apostrophe']; 79657d757d1SAndreas Gohr } 79757d757d1SAndreas Gohr 7983dd5c225SAndreas Gohr /** 7993dd5c225SAndreas Gohr * Render an opening double quote char (language specific) 8003dd5c225SAndreas Gohr */ 801de369923SAndreas Gohr public function doublequoteopening() { 80271b40da2SAnika Henke global $lang; 80371b40da2SAnika Henke $this->doc .= $lang['doublequoteopening']; 8040cecf9d5Sandi } 8050cecf9d5Sandi 8063dd5c225SAndreas Gohr /** 8073dd5c225SAndreas Gohr * Render an closinging double quote char (language specific) 8083dd5c225SAndreas Gohr */ 809de369923SAndreas Gohr public function doublequoteclosing() { 81071b40da2SAnika Henke global $lang; 81171b40da2SAnika Henke $this->doc .= $lang['doublequoteclosing']; 8120cecf9d5Sandi } 8130cecf9d5Sandi 8140cecf9d5Sandi /** 8153dd5c225SAndreas Gohr * Render a CamelCase link 8163dd5c225SAndreas Gohr * 8173dd5c225SAndreas Gohr * @param string $link The link name 818122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8190c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8200c4c0281SGerrit Uitslag * 8213dd5c225SAndreas Gohr * @see http://en.wikipedia.org/wiki/CamelCase 8220cecf9d5Sandi */ 823de369923SAndreas Gohr public function camelcaselink($link, $returnonly = false) { 824122f2d46SAndreas Böhler if($returnonly) { 825122f2d46SAndreas Böhler return $this->internallink($link, $link, null, true); 826122f2d46SAndreas Böhler } else { 82711d0aa47Sandi $this->internallink($link, $link); 8280cecf9d5Sandi } 829122f2d46SAndreas Böhler } 8300cecf9d5Sandi 8313dd5c225SAndreas Gohr /** 8323dd5c225SAndreas Gohr * Render a page local link 8333dd5c225SAndreas Gohr * 8343dd5c225SAndreas Gohr * @param string $hash hash link identifier 8353dd5c225SAndreas Gohr * @param string $name name for the link 836122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 8370c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 8383dd5c225SAndreas Gohr */ 839de369923SAndreas Gohr public function locallink($hash, $name = null, $returnonly = false) { 8400b7c14c2Sandi global $ID; 8410b7c14c2Sandi $name = $this->_getLinkTitle($name, $hash, $isImage); 8420b7c14c2Sandi $hash = $this->_headerToLink($hash); 843e260f93bSAnika Henke $title = $ID.' ↵'; 844122f2d46SAndreas Böhler 845122f2d46SAndreas Böhler $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 846122f2d46SAndreas Böhler $doc .= $name; 847122f2d46SAndreas Böhler $doc .= '</a>'; 848122f2d46SAndreas Böhler 849122f2d46SAndreas Böhler if($returnonly) { 850122f2d46SAndreas Böhler return $doc; 851122f2d46SAndreas Böhler } else { 852122f2d46SAndreas Böhler $this->doc .= $doc; 853122f2d46SAndreas Böhler } 8540b7c14c2Sandi } 8550b7c14c2Sandi 856cffcc403Sandi /** 8573fd0b676Sandi * Render an internal Wiki Link 8583fd0b676Sandi * 859fe9ec250SChris Smith * $search,$returnonly & $linktype are not for the renderer but are used 860cffcc403Sandi * elsewhere - no need to implement them in other renderers 8613fd0b676Sandi * 8623dd5c225SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 863f23eef27SGerrit Uitslag * @param string $id pageid 864f23eef27SGerrit Uitslag * @param string|null $name link name 865f23eef27SGerrit Uitslag * @param string|null $search adds search url param 866f23eef27SGerrit Uitslag * @param bool $returnonly whether to return html or write to doc attribute 867f23eef27SGerrit Uitslag * @param string $linktype type to set use of headings 868f23eef27SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 869cffcc403Sandi */ 870de369923SAndreas Gohr public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { 871ba11bd29Sandi global $conf; 87237e34a5eSandi global $ID; 873c4dda6afSAnika Henke global $INFO; 87444653a53SAdrian Lang 8753d5e07d9SAdrian Lang $params = ''; 8763d5e07d9SAdrian Lang $parts = explode('?', $id, 2); 8773d5e07d9SAdrian Lang if(count($parts) === 2) { 8783d5e07d9SAdrian Lang $id = $parts[0]; 8793d5e07d9SAdrian Lang $params = $parts[1]; 88044653a53SAdrian Lang } 88144653a53SAdrian Lang 882fda14ffcSIzidor Matušov // For empty $id we need to know the current $ID 883fda14ffcSIzidor Matušov // We need this check because _simpleTitle needs 884fda14ffcSIzidor Matušov // correct $id and resolve_pageid() use cleanID($id) 885fda14ffcSIzidor Matušov // (some things could be lost) 886fda14ffcSIzidor Matušov if($id === '') { 887fda14ffcSIzidor Matušov $id = $ID; 888fda14ffcSIzidor Matušov } 889fda14ffcSIzidor Matušov 8900339c872Sjan // default name is based on $id as given 8910339c872Sjan $default = $this->_simpleTitle($id); 892ad32e47eSAndreas Gohr 8930339c872Sjan // now first resolve and clean up the $id 89490bee600Slisps resolve_pageid(getNS($ID), $id, $exists, $this->date_at, true); 895fda14ffcSIzidor Matušov 89659bc3b48SGerrit Uitslag $link = array(); 897fe9ec250SChris Smith $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 8980e1c636eSandi if(!$isImage) { 8990e1c636eSandi if($exists) { 900ba11bd29Sandi $class = 'wikilink1'; 9010cecf9d5Sandi } else { 902ba11bd29Sandi $class = 'wikilink2'; 90344a6b4c7SAndreas Gohr $link['rel'] = 'nofollow'; 9040cecf9d5Sandi } 9050cecf9d5Sandi } else { 906ba11bd29Sandi $class = 'media'; 9070cecf9d5Sandi } 9080cecf9d5Sandi 909a1685bedSandi //keep hash anchor 9106d2af55dSChristopher Smith @list($id, $hash) = explode('#', $id, 2); 911943dedc6SAndreas Gohr if(!empty($hash)) $hash = $this->_headerToLink($hash); 912a1685bedSandi 913ba11bd29Sandi //prepare for formating 914ba11bd29Sandi $link['target'] = $conf['target']['wiki']; 915ba11bd29Sandi $link['style'] = ''; 916ba11bd29Sandi $link['pre'] = ''; 917ba11bd29Sandi $link['suf'] = ''; 918bbac1489SPhy $link['more'] = 'data-wiki-id="'.$id.'"'; // id is already cleaned 919ba11bd29Sandi $link['class'] = $class; 9205c2eed9aSlisps if($this->date_at) { 921912a6d48SPhy $params = $params.'&at='.rawurlencode($this->date_at); 9225c2eed9aSlisps } 92344653a53SAdrian Lang $link['url'] = wl($id, $params); 924ba11bd29Sandi $link['name'] = $name; 925ba11bd29Sandi $link['title'] = $id; 926723d78dbSandi //add search string 927723d78dbSandi if($search) { 928546d3a99SAndreas Gohr ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 929546d3a99SAndreas Gohr if(is_array($search)) { 930546d3a99SAndreas Gohr $search = array_map('rawurlencode', $search); 931546d3a99SAndreas Gohr $link['url'] .= 's[]='.join('&s[]=', $search); 932546d3a99SAndreas Gohr } else { 933546d3a99SAndreas Gohr $link['url'] .= 's='.rawurlencode($search); 934546d3a99SAndreas Gohr } 935723d78dbSandi } 936723d78dbSandi 937a1685bedSandi //keep hash 938a1685bedSandi if($hash) $link['url'] .= '#'.$hash; 939a1685bedSandi 940ba11bd29Sandi //output formatted 941cffcc403Sandi if($returnonly) { 942cffcc403Sandi return $this->_formatLink($link); 943cffcc403Sandi } else { 944a2d649c4Sandi $this->doc .= $this->_formatLink($link); 9450cecf9d5Sandi } 946cffcc403Sandi } 9470cecf9d5Sandi 9483dd5c225SAndreas Gohr /** 9493dd5c225SAndreas Gohr * Render an external link 9503dd5c225SAndreas Gohr * 9513dd5c225SAndreas Gohr * @param string $url full URL with scheme 9523dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 953122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 9540c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 9553dd5c225SAndreas Gohr */ 956de369923SAndreas Gohr public function externallink($url, $name = null, $returnonly = false) { 957b625487dSandi global $conf; 9580cecf9d5Sandi 959433bef32Sandi $name = $this->_getLinkTitle($name, $url, $isImage); 9606f0c5dbfSandi 961b52b1596SAndreas Gohr // url might be an attack vector, only allow registered protocols 962b52b1596SAndreas Gohr if(is_null($this->schemes)) $this->schemes = getSchemes(); 963b52b1596SAndreas Gohr list($scheme) = explode('://', $url); 964b52b1596SAndreas Gohr $scheme = strtolower($scheme); 965b52b1596SAndreas Gohr if(!in_array($scheme, $this->schemes)) $url = ''; 966b52b1596SAndreas Gohr 967b52b1596SAndreas Gohr // is there still an URL? 968b52b1596SAndreas Gohr if(!$url) { 96949cef4fdSAndreas Böhler if($returnonly) { 97049cef4fdSAndreas Böhler return $name; 97149cef4fdSAndreas Böhler } else { 972b52b1596SAndreas Gohr $this->doc .= $name; 97349cef4fdSAndreas Böhler } 974b52b1596SAndreas Gohr return; 975b52b1596SAndreas Gohr } 976b52b1596SAndreas Gohr 977b52b1596SAndreas Gohr // set class 9780cecf9d5Sandi if(!$isImage) { 979b625487dSandi $class = 'urlextern'; 9800cecf9d5Sandi } else { 981b625487dSandi $class = 'media'; 9820cecf9d5Sandi } 9830cecf9d5Sandi 984b625487dSandi //prepare for formating 98559bc3b48SGerrit Uitslag $link = array(); 986b625487dSandi $link['target'] = $conf['target']['extern']; 987b625487dSandi $link['style'] = ''; 988b625487dSandi $link['pre'] = ''; 989b625487dSandi $link['suf'] = ''; 9905e163278SAndreas Gohr $link['more'] = ''; 991b625487dSandi $link['class'] = $class; 992b625487dSandi $link['url'] = $url; 993914045f3SAndreas Gohr $link['rel'] = ''; 994e1c10e4dSchris 995b625487dSandi $link['name'] = $name; 996433bef32Sandi $link['title'] = $this->_xmlEntities($url); 9975ddd0bbbSStarArmy if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow'; 998914045f3SAndreas Gohr if($conf['target']['extern']) $link['rel'] .= ' noopener'; 9990cecf9d5Sandi 1000b625487dSandi //output formatted 1001122f2d46SAndreas Böhler if($returnonly) { 1002122f2d46SAndreas Böhler return $this->_formatLink($link); 1003122f2d46SAndreas Böhler } else { 1004a2d649c4Sandi $this->doc .= $this->_formatLink($link); 10050cecf9d5Sandi } 1006122f2d46SAndreas Böhler } 10070cecf9d5Sandi 10080cecf9d5Sandi /** 10093dd5c225SAndreas Gohr * Render an interwiki link 10103dd5c225SAndreas Gohr * 10113dd5c225SAndreas Gohr * You may want to use $this->_resolveInterWiki() here 10123dd5c225SAndreas Gohr * 10133dd5c225SAndreas Gohr * @param string $match original link - probably not much use 10143dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 10153dd5c225SAndreas Gohr * @param string $wikiName indentifier (shortcut) for the remote wiki 10163dd5c225SAndreas Gohr * @param string $wikiUri the fragment parsed from the original link 1017122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10180c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10190cecf9d5Sandi */ 1020de369923SAndreas Gohr public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { 1021b625487dSandi global $conf; 10220cecf9d5Sandi 102397a3e4e3Sandi $link = array(); 102497a3e4e3Sandi $link['target'] = $conf['target']['interwiki']; 102597a3e4e3Sandi $link['pre'] = ''; 102697a3e4e3Sandi $link['suf'] = ''; 10275e163278SAndreas Gohr $link['more'] = ''; 1028433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 1029914045f3SAndreas Gohr $link['rel'] = ''; 10300cecf9d5Sandi 103197a3e4e3Sandi //get interwiki URL 10326496c33fSGerrit Uitslag $exists = null; 10336496c33fSGerrit Uitslag $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists); 10340cecf9d5Sandi 103597a3e4e3Sandi if(!$isImage) { 10369d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 10379d2ddea4SAndreas Gohr $link['class'] = "interwiki iw_$class"; 10381c2d1019SAndreas Gohr } else { 10391c2d1019SAndreas Gohr $link['class'] = 'media'; 104097a3e4e3Sandi } 10410cecf9d5Sandi 104297a3e4e3Sandi //do we stay at the same server? Use local target 10432345e871SGerrit Uitslag if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 104497a3e4e3Sandi $link['target'] = $conf['target']['wiki']; 104597a3e4e3Sandi } 10466496c33fSGerrit Uitslag if($exists !== null && !$isImage) { 10476496c33fSGerrit Uitslag if($exists) { 10486496c33fSGerrit Uitslag $link['class'] .= ' wikilink1'; 10496496c33fSGerrit Uitslag } else { 10506496c33fSGerrit Uitslag $link['class'] .= ' wikilink2'; 1051914045f3SAndreas Gohr $link['rel'] .= ' nofollow'; 10526496c33fSGerrit Uitslag } 10536496c33fSGerrit Uitslag } 1054914045f3SAndreas Gohr if($conf['target']['interwiki']) $link['rel'] .= ' noopener'; 10550cecf9d5Sandi 105697a3e4e3Sandi $link['url'] = $url; 105797a3e4e3Sandi $link['title'] = htmlspecialchars($link['url']); 105897a3e4e3Sandi 105997a3e4e3Sandi // output formatted 1060122f2d46SAndreas Böhler if($returnonly) { 1061abde5980SPhy if($url == '') return $link['name']; 1062122f2d46SAndreas Böhler return $this->_formatLink($link); 1063122f2d46SAndreas Böhler } else { 1064abde5980SPhy if($url == '') $this->doc .= $link['name']; 1065abde5980SPhy else $this->doc .= $this->_formatLink($link); 10660cecf9d5Sandi } 1067122f2d46SAndreas Böhler } 10680cecf9d5Sandi 10690cecf9d5Sandi /** 10703dd5c225SAndreas Gohr * Link to windows share 10713dd5c225SAndreas Gohr * 10723dd5c225SAndreas Gohr * @param string $url the link 10733dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1074122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 10750c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 10760cecf9d5Sandi */ 1077de369923SAndreas Gohr public function windowssharelink($url, $name = null, $returnonly = false) { 10781d47afe1Sandi global $conf; 10793dd5c225SAndreas Gohr 10801d47afe1Sandi //simple setup 108159bc3b48SGerrit Uitslag $link = array(); 10821d47afe1Sandi $link['target'] = $conf['target']['windows']; 10831d47afe1Sandi $link['pre'] = ''; 10841d47afe1Sandi $link['suf'] = ''; 10851d47afe1Sandi $link['style'] = ''; 10860cecf9d5Sandi 1087433bef32Sandi $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 10880cecf9d5Sandi if(!$isImage) { 10891d47afe1Sandi $link['class'] = 'windows'; 10900cecf9d5Sandi } else { 10911d47afe1Sandi $link['class'] = 'media'; 10920cecf9d5Sandi } 10930cecf9d5Sandi 1094433bef32Sandi $link['title'] = $this->_xmlEntities($url); 10951d47afe1Sandi $url = str_replace('\\', '/', $url); 10961d47afe1Sandi $url = 'file:///'.$url; 10971d47afe1Sandi $link['url'] = $url; 10980cecf9d5Sandi 10991d47afe1Sandi //output formatted 1100122f2d46SAndreas Böhler if($returnonly) { 1101122f2d46SAndreas Böhler return $this->_formatLink($link); 1102122f2d46SAndreas Böhler } else { 1103a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11040cecf9d5Sandi } 1105122f2d46SAndreas Böhler } 11060cecf9d5Sandi 11073dd5c225SAndreas Gohr /** 11083dd5c225SAndreas Gohr * Render a linked E-Mail Address 11093dd5c225SAndreas Gohr * 11103dd5c225SAndreas Gohr * Honors $conf['mailguard'] setting 11113dd5c225SAndreas Gohr * 11123dd5c225SAndreas Gohr * @param string $address Email-Address 11133dd5c225SAndreas Gohr * @param string|array $name name for the link, array for media file 1114122f2d46SAndreas Böhler * @param bool $returnonly whether to return html or write to doc attribute 11150c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $returnonly 11163dd5c225SAndreas Gohr */ 1117de369923SAndreas Gohr public function emaillink($address, $name = null, $returnonly = false) { 111871352defSandi global $conf; 111971352defSandi //simple setup 112071352defSandi $link = array(); 112171352defSandi $link['target'] = ''; 112271352defSandi $link['pre'] = ''; 112371352defSandi $link['suf'] = ''; 112471352defSandi $link['style'] = ''; 112571352defSandi $link['more'] = ''; 11260cecf9d5Sandi 1127c078fc55SAndreas Gohr $name = $this->_getLinkTitle($name, '', $isImage); 11280cecf9d5Sandi if(!$isImage) { 1129be96545cSAnika Henke $link['class'] = 'mail'; 11300cecf9d5Sandi } else { 1131be96545cSAnika Henke $link['class'] = 'media'; 11320cecf9d5Sandi } 11330cecf9d5Sandi 113407738714SAndreas Gohr $address = $this->_xmlEntities($address); 113500a7b5adSEsther Brunner $address = obfuscate($address); 113600a7b5adSEsther Brunner $title = $address; 11378c128049SAndreas Gohr 113871352defSandi if(empty($name)) { 113900a7b5adSEsther Brunner $name = $address; 114071352defSandi } 11410cecf9d5Sandi 1142776b36ecSAndreas Gohr if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 1143776b36ecSAndreas Gohr 1144776b36ecSAndreas Gohr $link['url'] = 'mailto:'.$address; 114571352defSandi $link['name'] = $name; 114671352defSandi $link['title'] = $title; 11470cecf9d5Sandi 114871352defSandi //output formatted 1149122f2d46SAndreas Böhler if($returnonly) { 1150122f2d46SAndreas Böhler return $this->_formatLink($link); 1151122f2d46SAndreas Böhler } else { 1152a2d649c4Sandi $this->doc .= $this->_formatLink($link); 11530cecf9d5Sandi } 1154122f2d46SAndreas Böhler } 11550cecf9d5Sandi 11563dd5c225SAndreas Gohr /** 11573dd5c225SAndreas Gohr * Render an internal media file 11583dd5c225SAndreas Gohr * 11593dd5c225SAndreas Gohr * @param string $src media ID 11603dd5c225SAndreas Gohr * @param string $title descriptive text 11613dd5c225SAndreas Gohr * @param string $align left|center|right 11623dd5c225SAndreas Gohr * @param int $width width of media in pixel 11633dd5c225SAndreas Gohr * @param int $height height of media in pixel 11643dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 11653dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 11663dd5c225SAndreas Gohr * @param bool $return return HTML instead of adding to $doc 11670c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 11683dd5c225SAndreas Gohr */ 1169de369923SAndreas Gohr public function internalmedia($src, $title = null, $align = null, $width = null, 11703dd5c225SAndreas Gohr $height = null, $cache = null, $linking = null, $return = false) { 117137e34a5eSandi global $ID; 11728f34cf3dSMichael Große if (strpos($src, '#') !== false) { 117391df343aSAndreas Gohr list($src, $hash) = explode('#', $src, 2); 11748f34cf3dSMichael Große } 1175cdb5e961Slisps resolve_mediaid(getNS($ID), $src, $exists, $this->date_at, true); 11760cecf9d5Sandi 1177d98d4540SBen Coburn $noLink = false; 11788acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1179b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 11803685f775Sandi 11813dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1182b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 118364159a61SAndreas Gohr $link['url'] = ml( 118464159a61SAndreas Gohr $src, 118564159a61SAndreas Gohr array( 118664159a61SAndreas Gohr 'id' => $ID, 118764159a61SAndreas Gohr 'cache' => $cache, 118864159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 118964159a61SAndreas Gohr ), 119064159a61SAndreas Gohr ($linking == 'direct') 119164159a61SAndreas Gohr ); 1192f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 11932a2a2ba2SAnika Henke // don't link movies 119444881bd0Shenning.noren $noLink = true; 119555efc227SAndreas Gohr } else { 11962ca14335SEsther Brunner // add file icons 11979d2ddea4SAndreas Gohr $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 11989d2ddea4SAndreas Gohr $link['class'] .= ' mediafile mf_'.$class; 119964159a61SAndreas Gohr $link['url'] = ml( 120064159a61SAndreas Gohr $src, 120164159a61SAndreas Gohr array( 120264159a61SAndreas Gohr 'id' => $ID, 120364159a61SAndreas Gohr 'cache' => $cache, 120464159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 120564159a61SAndreas Gohr ), 120664159a61SAndreas Gohr true 120764159a61SAndreas Gohr ); 120891328684SMichael Hamann if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; 120955efc227SAndreas Gohr } 12103685f775Sandi 12118f34cf3dSMichael Große if (!empty($hash)) $link['url'] .= '#'.$hash; 121291df343aSAndreas Gohr 12136fe20453SGina Haeussge //markup non existing files 12144a24b459SKate Arzamastseva if(!$exists) { 12156fe20453SGina Haeussge $link['class'] .= ' wikilink2'; 12164a24b459SKate Arzamastseva } 12176fe20453SGina Haeussge 12183685f775Sandi //output formatted 1219f50634f0SAnika Henke if($return) { 1220f50634f0SAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1221f50634f0SAnika Henke else return $this->_formatLink($link); 1222f50634f0SAnika Henke } else { 1223dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 12242ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 12250cecf9d5Sandi } 1226f50634f0SAnika Henke } 12270cecf9d5Sandi 12283dd5c225SAndreas Gohr /** 12293dd5c225SAndreas Gohr * Render an external media file 12303dd5c225SAndreas Gohr * 12313dd5c225SAndreas Gohr * @param string $src full media URL 12323dd5c225SAndreas Gohr * @param string $title descriptive text 12333dd5c225SAndreas Gohr * @param string $align left|center|right 12343dd5c225SAndreas Gohr * @param int $width width of media in pixel 12353dd5c225SAndreas Gohr * @param int $height height of media in pixel 12363dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 12373dd5c225SAndreas Gohr * @param string $linking linkonly|detail|nolink 1238410ee62aSAnika Henke * @param bool $return return HTML instead of adding to $doc 12390c4c0281SGerrit Uitslag * @return void|string writes to doc attribute or returns html depends on $return 12403dd5c225SAndreas Gohr */ 1241de369923SAndreas Gohr public function externalmedia($src, $title = null, $align = null, $width = null, 1242410ee62aSAnika Henke $height = null, $cache = null, $linking = null, $return = false) { 12436efc45a2SDmitry Katsubo if(link_isinterwiki($src)){ 12446efc45a2SDmitry Katsubo list($shortcut, $reference) = explode('>', $src, 2); 12456efc45a2SDmitry Katsubo $exists = null; 12466efc45a2SDmitry Katsubo $src = $this->_resolveInterWiki($shortcut, $reference, $exists); 1247abde5980SPhy if($src == '' && empty($title)){ 1248abde5980SPhy // make sure at least something will be shown in this case 1249abde5980SPhy $title = $reference; 1250abde5980SPhy } 12516efc45a2SDmitry Katsubo } 1252*c9dd70d1SDamien Regad // Squelch the warning in case there is no hash in the URL 1253*c9dd70d1SDamien Regad @list($src, $hash) = explode('#', $src, 2); 1254d98d4540SBen Coburn $noLink = false; 1255abde5980SPhy if($src == '') { 1256abde5980SPhy // only output plaintext without link if there is no src 1257abde5980SPhy $noLink = true; 1258abde5980SPhy } 12598acb3108SAndreas Gohr $render = ($linking == 'linkonly') ? false : true; 1260b739ff0fSPierre Spring $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 1261b739ff0fSPierre Spring 1262b739ff0fSPierre Spring $link['url'] = ml($src, array('cache' => $cache)); 12633685f775Sandi 12643dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src, false); 1265b739ff0fSPierre Spring if(substr($mime, 0, 5) == 'image' && $render) { 12662ca14335SEsther Brunner // link only jpeg images 126744881bd0Shenning.noren // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 1268f50634f0SAnika Henke } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { 12692a2a2ba2SAnika Henke // don't link movies 127044881bd0Shenning.noren $noLink = true; 12712ca14335SEsther Brunner } else { 12722ca14335SEsther Brunner // add file icons 127327bf7924STom N Harris $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 127427bf7924STom N Harris $link['class'] .= ' mediafile mf_'.$class; 12752ca14335SEsther Brunner } 12762ca14335SEsther Brunner 127791df343aSAndreas Gohr if($hash) $link['url'] .= '#'.$hash; 127891df343aSAndreas Gohr 12793685f775Sandi //output formatted 1280410ee62aSAnika Henke if($return) { 1281410ee62aSAnika Henke if($linking == 'nolink' || $noLink) return $link['name']; 1282410ee62aSAnika Henke else return $this->_formatLink($link); 1283410ee62aSAnika Henke } else { 1284dc673a5bSjoe.lapp if($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 12852ca14335SEsther Brunner else $this->doc .= $this->_formatLink($link); 12860cecf9d5Sandi } 1287410ee62aSAnika Henke } 12880cecf9d5Sandi 12894826ab45Sandi /** 12903db95becSAndreas Gohr * Renders an RSS feed 1291b625487dSandi * 12920c4c0281SGerrit Uitslag * @param string $url URL of the feed 12930c4c0281SGerrit Uitslag * @param array $params Finetuning of the output 12940c4c0281SGerrit Uitslag * 1295b625487dSandi * @author Andreas Gohr <andi@splitbrain.org> 1296b625487dSandi */ 1297de369923SAndreas Gohr public function rss($url, $params) { 1298b625487dSandi global $lang; 12993db95becSAndreas Gohr global $conf; 13003db95becSAndreas Gohr 13013db95becSAndreas Gohr require_once(DOKU_INC.'inc/FeedParser.php'); 13023db95becSAndreas Gohr $feed = new FeedParser(); 130300077af8SAndreas Gohr $feed->set_feed_url($url); 1304b625487dSandi 1305b625487dSandi //disable warning while fetching 13063dd5c225SAndreas Gohr if(!defined('DOKU_E_LEVEL')) { 13073dd5c225SAndreas Gohr $elvl = error_reporting(E_ERROR); 13083dd5c225SAndreas Gohr } 13093db95becSAndreas Gohr $rc = $feed->init(); 13103dd5c225SAndreas Gohr if(isset($elvl)) { 13113dd5c225SAndreas Gohr error_reporting($elvl); 13123dd5c225SAndreas Gohr } 1313b625487dSandi 131438c6f603SRobin H. Johnson if($params['nosort']) $feed->enable_order_by_date(false); 131538c6f603SRobin H. Johnson 13163db95becSAndreas Gohr //decide on start and end 13173db95becSAndreas Gohr if($params['reverse']) { 13183db95becSAndreas Gohr $mod = -1; 13193db95becSAndreas Gohr $start = $feed->get_item_quantity() - 1; 13203db95becSAndreas Gohr $end = $start - ($params['max']); 1321b2a412b0SAndreas Gohr $end = ($end < -1) ? -1 : $end; 13223db95becSAndreas Gohr } else { 13233db95becSAndreas Gohr $mod = 1; 13243db95becSAndreas Gohr $start = 0; 13253db95becSAndreas Gohr $end = $feed->get_item_quantity(); 1326d91ab76fSMatt Perry $end = ($end > $params['max']) ? $params['max'] : $end; 13273db95becSAndreas Gohr } 13283db95becSAndreas Gohr 1329a2d649c4Sandi $this->doc .= '<ul class="rss">'; 13303db95becSAndreas Gohr if($rc) { 13313db95becSAndreas Gohr for($x = $start; $x != $end; $x += $mod) { 13321bde1582SAndreas Gohr $item = $feed->get_item($x); 13333db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1334d2ea3363SAndreas Gohr // support feeds without links 1335d2ea3363SAndreas Gohr $lnkurl = $item->get_permalink(); 1336d2ea3363SAndreas Gohr if($lnkurl) { 1337793361f8SAndreas Gohr // title is escaped by SimplePie, we unescape here because it 1338793361f8SAndreas Gohr // is escaped again in externallink() FS#1705 13393dd5c225SAndreas Gohr $this->externallink( 13403dd5c225SAndreas Gohr $item->get_permalink(), 13413dd5c225SAndreas Gohr html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8') 13423dd5c225SAndreas Gohr ); 1343d2ea3363SAndreas Gohr } else { 1344d2ea3363SAndreas Gohr $this->doc .= ' '.$item->get_title(); 1345d2ea3363SAndreas Gohr } 13463db95becSAndreas Gohr if($params['author']) { 13471bde1582SAndreas Gohr $author = $item->get_author(0); 13481bde1582SAndreas Gohr if($author) { 13491bde1582SAndreas Gohr $name = $author->get_name(); 13501bde1582SAndreas Gohr if(!$name) $name = $author->get_email(); 1351163c2842SPhy if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name); 13521bde1582SAndreas Gohr } 13533db95becSAndreas Gohr } 13543db95becSAndreas Gohr if($params['date']) { 13552e7e0c29SAndreas Gohr $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 13563db95becSAndreas Gohr } 13571bde1582SAndreas Gohr if($params['details']) { 13583db95becSAndreas Gohr $this->doc .= '<div class="detail">'; 1359173dccb7STom N Harris if($conf['htmlok']) { 13601bde1582SAndreas Gohr $this->doc .= $item->get_description(); 13613db95becSAndreas Gohr } else { 13621bde1582SAndreas Gohr $this->doc .= strip_tags($item->get_description()); 13633db95becSAndreas Gohr } 13643db95becSAndreas Gohr $this->doc .= '</div>'; 13653db95becSAndreas Gohr } 13663db95becSAndreas Gohr 13673db95becSAndreas Gohr $this->doc .= '</div></li>'; 1368b625487dSandi } 1369b625487dSandi } else { 13703db95becSAndreas Gohr $this->doc .= '<li><div class="li">'; 1371a2d649c4Sandi $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 1372b625487dSandi $this->externallink($url); 137345e147ccSAndreas Gohr if($conf['allowdebug']) { 137445e147ccSAndreas Gohr $this->doc .= '<!--'.hsc($feed->error).'-->'; 137545e147ccSAndreas Gohr } 13763db95becSAndreas Gohr $this->doc .= '</div></li>'; 1377b625487dSandi } 1378a2d649c4Sandi $this->doc .= '</ul>'; 1379b625487dSandi } 1380b625487dSandi 13813dd5c225SAndreas Gohr /** 13823dd5c225SAndreas Gohr * Start a table 13833dd5c225SAndreas Gohr * 13843dd5c225SAndreas Gohr * @param int $maxcols maximum number of columns 13853dd5c225SAndreas Gohr * @param int $numrows NOT IMPLEMENTED 13863dd5c225SAndreas Gohr * @param int $pos byte position in the original source 13877d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 13883dd5c225SAndreas Gohr */ 1389de369923SAndreas Gohr public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { 1390b5742cedSPierre Spring // initialize the row counter used for classes 1391b5742cedSPierre Spring $this->_counter['row_counter'] = 0; 1392619736fdSAdrian Lang $class = 'table'; 13930c4c0281SGerrit Uitslag if($classes !== null) { 13942e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 13950c4c0281SGerrit Uitslag $class .= ' ' . $classes; 13960c4c0281SGerrit Uitslag } 1397619736fdSAdrian Lang if($pos !== null) { 139806917fceSMichael Große $hid = $this->_headerToLink($class, true); 1399ec57f119SLarsDW223 $data = array(); 1400ec57f119SLarsDW223 $data['target'] = 'table'; 1401ec57f119SLarsDW223 $data['name'] = ''; 1402ec57f119SLarsDW223 $data['hid'] = $hid; 1403ec57f119SLarsDW223 $class .= ' '.$this->startSectionEdit($pos, $data); 1404619736fdSAdrian Lang } 1405619736fdSAdrian Lang $this->doc .= '<div class="'.$class.'"><table class="inline">'. 1406619736fdSAdrian Lang DOKU_LF; 14070cecf9d5Sandi } 14080cecf9d5Sandi 14093dd5c225SAndreas Gohr /** 14103dd5c225SAndreas Gohr * Close a table 14113dd5c225SAndreas Gohr * 14123dd5c225SAndreas Gohr * @param int $pos byte position in the original source 14133dd5c225SAndreas Gohr */ 1414de369923SAndreas Gohr public function table_close($pos = null) { 1415a8574918SAnika Henke $this->doc .= '</table></div>'.DOKU_LF; 1416619736fdSAdrian Lang if($pos !== null) { 141790df9a4dSAdrian Lang $this->finishSectionEdit($pos); 14180cecf9d5Sandi } 1419619736fdSAdrian Lang } 14200cecf9d5Sandi 14213dd5c225SAndreas Gohr /** 14223dd5c225SAndreas Gohr * Open a table header 14233dd5c225SAndreas Gohr */ 1424de369923SAndreas Gohr public function tablethead_open() { 1425f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 1426f05a1cc5SGerrit Uitslag } 1427f05a1cc5SGerrit Uitslag 14283dd5c225SAndreas Gohr /** 14293dd5c225SAndreas Gohr * Close a table header 14303dd5c225SAndreas Gohr */ 1431de369923SAndreas Gohr public function tablethead_close() { 1432f05a1cc5SGerrit Uitslag $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 1433f05a1cc5SGerrit Uitslag } 1434f05a1cc5SGerrit Uitslag 14353dd5c225SAndreas Gohr /** 14365a93f869SAnika Henke * Open a table body 14375a93f869SAnika Henke */ 1438de369923SAndreas Gohr public function tabletbody_open() { 14395a93f869SAnika Henke $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF; 14405a93f869SAnika Henke } 14415a93f869SAnika Henke 14425a93f869SAnika Henke /** 14435a93f869SAnika Henke * Close a table body 14445a93f869SAnika Henke */ 1445de369923SAndreas Gohr public function tabletbody_close() { 14465a93f869SAnika Henke $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF; 14475a93f869SAnika Henke } 14485a93f869SAnika Henke 14495a93f869SAnika Henke /** 1450d2a99739SAndreas Gohr * Open a table footer 1451d2a99739SAndreas Gohr */ 1452de369923SAndreas Gohr public function tabletfoot_open() { 145344f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF; 1454d2a99739SAndreas Gohr } 1455d2a99739SAndreas Gohr 1456d2a99739SAndreas Gohr /** 1457d2a99739SAndreas Gohr * Close a table footer 1458d2a99739SAndreas Gohr */ 1459de369923SAndreas Gohr public function tabletfoot_close() { 146044f5d1c1SAndreas Gohr $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF; 1461d2a99739SAndreas Gohr } 1462d2a99739SAndreas Gohr 1463d2a99739SAndreas Gohr /** 14643dd5c225SAndreas Gohr * Open a table row 14650c4c0281SGerrit Uitslag * 14667d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14673dd5c225SAndreas Gohr */ 1468de369923SAndreas Gohr public function tablerow_open($classes = null) { 1469b5742cedSPierre Spring // initialize the cell counter used for classes 1470b5742cedSPierre Spring $this->_counter['cell_counter'] = 0; 1471b5742cedSPierre Spring $class = 'row'.$this->_counter['row_counter']++; 14720c4c0281SGerrit Uitslag if($classes !== null) { 14732e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 14740c4c0281SGerrit Uitslag $class .= ' ' . $classes; 14750c4c0281SGerrit Uitslag } 1476b5742cedSPierre Spring $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB; 14770cecf9d5Sandi } 14780cecf9d5Sandi 14793dd5c225SAndreas Gohr /** 14803dd5c225SAndreas Gohr * Close a table row 14813dd5c225SAndreas Gohr */ 1482de369923SAndreas Gohr public function tablerow_close() { 1483a2d649c4Sandi $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 14840cecf9d5Sandi } 14850cecf9d5Sandi 14863dd5c225SAndreas Gohr /** 14873dd5c225SAndreas Gohr * Open a table header cell 14883dd5c225SAndreas Gohr * 14893dd5c225SAndreas Gohr * @param int $colspan 14903dd5c225SAndreas Gohr * @param string $align left|center|right 14913dd5c225SAndreas Gohr * @param int $rowspan 14927d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 14933dd5c225SAndreas Gohr */ 1494de369923SAndreas Gohr public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1495b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 14960cecf9d5Sandi if(!is_null($align)) { 1497b5742cedSPierre Spring $class .= ' '.$align.'align'; 14980cecf9d5Sandi } 14990c4c0281SGerrit Uitslag if($classes !== null) { 15002e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 15010c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15020c4c0281SGerrit Uitslag } 1503b5742cedSPierre Spring $class .= '"'; 1504b5742cedSPierre Spring $this->doc .= '<th '.$class; 15050cecf9d5Sandi if($colspan > 1) { 1506a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1507a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 15080cecf9d5Sandi } 150925b97867Shakan.sandell if($rowspan > 1) { 151025b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 151125b97867Shakan.sandell } 1512a2d649c4Sandi $this->doc .= '>'; 15130cecf9d5Sandi } 15140cecf9d5Sandi 15153dd5c225SAndreas Gohr /** 15163dd5c225SAndreas Gohr * Close a table header cell 15173dd5c225SAndreas Gohr */ 1518de369923SAndreas Gohr public function tableheader_close() { 1519a2d649c4Sandi $this->doc .= '</th>'; 15200cecf9d5Sandi } 15210cecf9d5Sandi 15223dd5c225SAndreas Gohr /** 15233dd5c225SAndreas Gohr * Open a table cell 15243dd5c225SAndreas Gohr * 15253dd5c225SAndreas Gohr * @param int $colspan 15263dd5c225SAndreas Gohr * @param string $align left|center|right 15273dd5c225SAndreas Gohr * @param int $rowspan 15287d769f75SAndreas Gohr * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input 15293dd5c225SAndreas Gohr */ 1530de369923SAndreas Gohr public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { 1531b5742cedSPierre Spring $class = 'class="col'.$this->_counter['cell_counter']++; 15320cecf9d5Sandi if(!is_null($align)) { 1533b5742cedSPierre Spring $class .= ' '.$align.'align'; 15340cecf9d5Sandi } 15350c4c0281SGerrit Uitslag if($classes !== null) { 15362e0ebe60SAndreas Gohr if(is_array($classes)) $classes = join(' ', $classes); 15370c4c0281SGerrit Uitslag $class .= ' ' . $classes; 15380c4c0281SGerrit Uitslag } 1539b5742cedSPierre Spring $class .= '"'; 1540b5742cedSPierre Spring $this->doc .= '<td '.$class; 15410cecf9d5Sandi if($colspan > 1) { 1542a28fd914SAndreas Gohr $this->_counter['cell_counter'] += $colspan - 1; 1543a2d649c4Sandi $this->doc .= ' colspan="'.$colspan.'"'; 15440cecf9d5Sandi } 154525b97867Shakan.sandell if($rowspan > 1) { 154625b97867Shakan.sandell $this->doc .= ' rowspan="'.$rowspan.'"'; 154725b97867Shakan.sandell } 1548a2d649c4Sandi $this->doc .= '>'; 15490cecf9d5Sandi } 15500cecf9d5Sandi 15513dd5c225SAndreas Gohr /** 15523dd5c225SAndreas Gohr * Close a table cell 15533dd5c225SAndreas Gohr */ 1554de369923SAndreas Gohr public function tablecell_close() { 1555a2d649c4Sandi $this->doc .= '</td>'; 15560cecf9d5Sandi } 15570cecf9d5Sandi 1558cea664bdSLarsDW223 /** 1559cea664bdSLarsDW223 * Returns the current header level. 1560cea664bdSLarsDW223 * (required e.g. by the filelist plugin) 1561cea664bdSLarsDW223 * 1562cea664bdSLarsDW223 * @return int The current header level 1563cea664bdSLarsDW223 */ 1564de369923SAndreas Gohr public function getLastlevel() { 1565cea664bdSLarsDW223 return $this->lastlevel; 1566cea664bdSLarsDW223 } 1567cea664bdSLarsDW223 15683dd5c225SAndreas Gohr #region Utility functions 15690cecf9d5Sandi 1570ba11bd29Sandi /** 15713fd0b676Sandi * Build a link 15723fd0b676Sandi * 15733fd0b676Sandi * Assembles all parts defined in $link returns HTML for the link 1574ba11bd29Sandi * 15750c4c0281SGerrit Uitslag * @param array $link attributes of a link 15760c4c0281SGerrit Uitslag * @return string 15770c4c0281SGerrit Uitslag * 1578ba11bd29Sandi * @author Andreas Gohr <andi@splitbrain.org> 1579ba11bd29Sandi */ 1580de369923SAndreas Gohr public function _formatLink($link) { 1581ba11bd29Sandi //make sure the url is XHTML compliant (skip mailto) 1582ba11bd29Sandi if(substr($link['url'], 0, 7) != 'mailto:') { 1583ba11bd29Sandi $link['url'] = str_replace('&', '&', $link['url']); 1584ba11bd29Sandi $link['url'] = str_replace('&amp;', '&', $link['url']); 1585ba11bd29Sandi } 1586ba11bd29Sandi //remove double encodings in titles 1587ba11bd29Sandi $link['title'] = str_replace('&amp;', '&', $link['title']); 1588ba11bd29Sandi 1589453493f2SAndreas Gohr // be sure there are no bad chars in url or title 1590453493f2SAndreas Gohr // (we can't do this for name because it can contain an img tag) 1591453493f2SAndreas Gohr $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22')); 1592453493f2SAndreas Gohr $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"')); 1593453493f2SAndreas Gohr 1594ba11bd29Sandi $ret = ''; 1595ba11bd29Sandi $ret .= $link['pre']; 1596ba11bd29Sandi $ret .= '<a href="'.$link['url'].'"'; 1597bb4866bdSchris if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 1598bb4866bdSchris if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 1599bb4866bdSchris if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 1600bb4866bdSchris if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 1601914045f3SAndreas Gohr if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"'; 1602bb4866bdSchris if(!empty($link['more'])) $ret .= ' '.$link['more']; 1603ba11bd29Sandi $ret .= '>'; 1604ba11bd29Sandi $ret .= $link['name']; 1605ba11bd29Sandi $ret .= '</a>'; 1606ba11bd29Sandi $ret .= $link['suf']; 1607ba11bd29Sandi return $ret; 1608ba11bd29Sandi } 1609ba11bd29Sandi 1610ba11bd29Sandi /** 16113fd0b676Sandi * Renders internal and external media 16123fd0b676Sandi * 16133fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 16143dd5c225SAndreas Gohr * @param string $src media ID 16153dd5c225SAndreas Gohr * @param string $title descriptive text 16163dd5c225SAndreas Gohr * @param string $align left|center|right 16173dd5c225SAndreas Gohr * @param int $width width of media in pixel 16183dd5c225SAndreas Gohr * @param int $height height of media in pixel 16193dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 16203dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 16213dd5c225SAndreas Gohr * @return string 16223fd0b676Sandi */ 1623de369923SAndreas Gohr public function _media($src, $title = null, $align = null, $width = null, 16240ea51e63SMatt Perry $height = null, $cache = null, $render = true) { 16253fd0b676Sandi 16263fd0b676Sandi $ret = ''; 16273fd0b676Sandi 16283dd5c225SAndreas Gohr list($ext, $mime) = mimetype($src); 16293fd0b676Sandi if(substr($mime, 0, 5) == 'image') { 1630b739ff0fSPierre Spring // first get the $title 1631b739ff0fSPierre Spring if(!is_null($title)) { 1632b739ff0fSPierre Spring $title = $this->_xmlEntities($title); 1633b739ff0fSPierre Spring } elseif($ext == 'jpg' || $ext == 'jpeg') { 1634b739ff0fSPierre Spring //try to use the caption from IPTC/EXIF 1635b739ff0fSPierre Spring require_once(DOKU_INC.'inc/JpegMeta.php'); 163667f9913dSAndreas Gohr $jpeg = new JpegMeta(mediaFN($src)); 1637b739ff0fSPierre Spring if($jpeg !== false) $cap = $jpeg->getTitle(); 16383dd5c225SAndreas Gohr if(!empty($cap)) { 1639b739ff0fSPierre Spring $title = $this->_xmlEntities($cap); 1640b739ff0fSPierre Spring } 1641b739ff0fSPierre Spring } 1642b739ff0fSPierre Spring if(!$render) { 1643b739ff0fSPierre Spring // if the picture is not supposed to be rendered 1644b739ff0fSPierre Spring // return the title of the picture 1645768be5a3SPhy if($title === null || $title === "") { 1646b739ff0fSPierre Spring // just show the sourcename 16478cbc5ee8SAndreas Gohr $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 1648b739ff0fSPierre Spring } 1649b739ff0fSPierre Spring return $title; 1650b739ff0fSPierre Spring } 16513fd0b676Sandi //add image tag 165264159a61SAndreas Gohr $ret .= '<img src="' . ml( 165364159a61SAndreas Gohr $src, 165464159a61SAndreas Gohr array( 165564159a61SAndreas Gohr 'w' => $width, 'h' => $height, 165664159a61SAndreas Gohr 'cache' => $cache, 165764159a61SAndreas Gohr 'rev' => $this->_getLastMediaRevisionAt($src) 165864159a61SAndreas Gohr ) 165964159a61SAndreas Gohr ) . '"'; 16603fd0b676Sandi $ret .= ' class="media'.$align.'"'; 16613fd0b676Sandi 1662b739ff0fSPierre Spring if($title) { 1663b739ff0fSPierre Spring $ret .= ' title="'.$title.'"'; 1664b739ff0fSPierre Spring $ret .= ' alt="'.$title.'"'; 16653fd0b676Sandi } else { 16663fd0b676Sandi $ret .= ' alt=""'; 16673fd0b676Sandi } 16683fd0b676Sandi 16693fd0b676Sandi if(!is_null($width)) 16703fd0b676Sandi $ret .= ' width="'.$this->_xmlEntities($width).'"'; 16713fd0b676Sandi 16723fd0b676Sandi if(!is_null($height)) 16733fd0b676Sandi $ret .= ' height="'.$this->_xmlEntities($height).'"'; 16743fd0b676Sandi 16753fd0b676Sandi $ret .= ' />'; 16763fd0b676Sandi 167717954bb5SAnika Henke } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) { 16782a2a2ba2SAnika Henke // first get the $title 1679a8dcb874Sbleistivt $title = !is_null($title) ? $title : false; 16802a2a2ba2SAnika Henke if(!$render) { 168117954bb5SAnika Henke // if the file is not supposed to be rendered 168217954bb5SAnika Henke // return the title of the file (just the sourcename if there is no title) 1683a8dcb874Sbleistivt return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src))); 16842a2a2ba2SAnika Henke } 16852a2a2ba2SAnika Henke 16862a2a2ba2SAnika Henke $att = array(); 16872a2a2ba2SAnika Henke $att['class'] = "media$align"; 168817954bb5SAnika Henke if($title) { 168917954bb5SAnika Henke $att['title'] = $title; 169017954bb5SAnika Henke } 16912a2a2ba2SAnika Henke 169217954bb5SAnika Henke if(media_supportedav($mime, 'video')) { 169317954bb5SAnika Henke //add video 169479e53fe5SAnika Henke $ret .= $this->_video($src, $width, $height, $att); 1695b44a5dceSAnika Henke } 169617954bb5SAnika Henke if(media_supportedav($mime, 'audio')) { 1697b44a5dceSAnika Henke //add audio 1698b44a5dceSAnika Henke $ret .= $this->_audio($src, $att); 169917954bb5SAnika Henke } 1700b44a5dceSAnika Henke 17013fd0b676Sandi } elseif($mime == 'application/x-shockwave-flash') { 17021c882ba8SAndreas Gohr if(!$render) { 17031c882ba8SAndreas Gohr // if the flash is not supposed to be rendered 17041c882ba8SAndreas Gohr // return the title of the flash 17051c882ba8SAndreas Gohr if(!$title) { 17061c882ba8SAndreas Gohr // just show the sourcename 17078cbc5ee8SAndreas Gohr $title = \dokuwiki\Utf8\PhpString::basename(noNS($src)); 17081c882ba8SAndreas Gohr } 170907bf32b2SAndreas Gohr return $this->_xmlEntities($title); 17101c882ba8SAndreas Gohr } 17111c882ba8SAndreas Gohr 171207bf32b2SAndreas Gohr $att = array(); 171307bf32b2SAndreas Gohr $att['class'] = "media$align"; 171407bf32b2SAndreas Gohr if($align == 'right') $att['align'] = 'right'; 171507bf32b2SAndreas Gohr if($align == 'left') $att['align'] = 'left'; 17163dd5c225SAndreas Gohr $ret .= html_flashobject( 17173dd5c225SAndreas Gohr ml($src, array('cache' => $cache), true, '&'), $width, $height, 171807bf32b2SAndreas Gohr array('quality' => 'high'), 171907bf32b2SAndreas Gohr null, 172007bf32b2SAndreas Gohr $att, 17213dd5c225SAndreas Gohr $this->_xmlEntities($title) 17223dd5c225SAndreas Gohr ); 17230f428d7dSAndreas Gohr } elseif($title) { 17243fd0b676Sandi // well at least we have a title to display 17253fd0b676Sandi $ret .= $this->_xmlEntities($title); 17263fd0b676Sandi } else { 17275291ca3aSAndreas Gohr // just show the sourcename 17288cbc5ee8SAndreas Gohr $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); 17293fd0b676Sandi } 17303fd0b676Sandi 17313fd0b676Sandi return $ret; 17323fd0b676Sandi } 17333fd0b676Sandi 17343dd5c225SAndreas Gohr /** 17353dd5c225SAndreas Gohr * Escape string for output 17363dd5c225SAndreas Gohr * 17373dd5c225SAndreas Gohr * @param $string 17383dd5c225SAndreas Gohr * @return string 17393dd5c225SAndreas Gohr */ 1740de369923SAndreas Gohr public function _xmlEntities($string) { 1741de117061Schris return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); 17420cecf9d5Sandi } 17430cecf9d5Sandi 1744de369923SAndreas Gohr 17450cecf9d5Sandi 1746af587fa8Sandi /** 17473fd0b676Sandi * Construct a title and handle images in titles 17483fd0b676Sandi * 17490b7c14c2Sandi * @author Harry Fuecks <hfuecks@gmail.com> 17503dd5c225SAndreas Gohr * @param string|array $title either string title or media array 17513dd5c225SAndreas Gohr * @param string $default default title if nothing else is found 17523dd5c225SAndreas Gohr * @param bool $isImage will be set to true if it's a media file 17533dd5c225SAndreas Gohr * @param null|string $id linked page id (used to extract title from first heading) 17543dd5c225SAndreas Gohr * @param string $linktype content|navigation 17553dd5c225SAndreas Gohr * @return string HTML of the title, might be full image tag or just escaped text 17563fd0b676Sandi */ 1757de369923SAndreas Gohr public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { 175844881bd0Shenning.noren $isImage = false; 175929657f9eSAndreas Gohr if(is_array($title)) { 176029657f9eSAndreas Gohr $isImage = true; 176129657f9eSAndreas Gohr return $this->_imageTitle($title); 176229657f9eSAndreas Gohr } elseif(is_null($title) || trim($title) == '') { 1763fe9ec250SChris Smith if(useHeading($linktype) && $id) { 176467c15eceSMichael Hamann $heading = p_get_first_heading($id); 1765f515db7fSAndreas Gohr if(!blank($heading)) { 1766433bef32Sandi return $this->_xmlEntities($heading); 1767bb0a59d4Sjan } 1768bb0a59d4Sjan } 1769433bef32Sandi return $this->_xmlEntities($default); 177068c26e6dSMichael Klier } else { 177168c26e6dSMichael Klier return $this->_xmlEntities($title); 17720cecf9d5Sandi } 17730cecf9d5Sandi } 17740cecf9d5Sandi 17750cecf9d5Sandi /** 17763dd5c225SAndreas Gohr * Returns HTML code for images used in link titles 17773fd0b676Sandi * 17783fd0b676Sandi * @author Andreas Gohr <andi@splitbrain.org> 1779e0c26282SGerrit Uitslag * @param array $img 17803dd5c225SAndreas Gohr * @return string HTML img tag or similar 17810cecf9d5Sandi */ 1782de369923SAndreas Gohr public function _imageTitle($img) { 1783d9baf1a7SKazutaka Miyasaka global $ID; 1784d9baf1a7SKazutaka Miyasaka 1785d9baf1a7SKazutaka Miyasaka // some fixes on $img['src'] 1786d9baf1a7SKazutaka Miyasaka // see internalmedia() and externalmedia() 17873dd5c225SAndreas Gohr list($img['src']) = explode('#', $img['src'], 2); 1788d9baf1a7SKazutaka Miyasaka if($img['type'] == 'internalmedia') { 1789cdb5e961Slisps resolve_mediaid(getNS($ID), $img['src'], $exists ,$this->date_at, true); 1790d9baf1a7SKazutaka Miyasaka } 1791d9baf1a7SKazutaka Miyasaka 17923dd5c225SAndreas Gohr return $this->_media( 17933dd5c225SAndreas Gohr $img['src'], 17944826ab45Sandi $img['title'], 17954826ab45Sandi $img['align'], 17964826ab45Sandi $img['width'], 17974826ab45Sandi $img['height'], 17983dd5c225SAndreas Gohr $img['cache'] 17993dd5c225SAndreas Gohr ); 18000cecf9d5Sandi } 1801b739ff0fSPierre Spring 1802b739ff0fSPierre Spring /** 18033dd5c225SAndreas Gohr * helperfunction to return a basic link to a media 18043dd5c225SAndreas Gohr * 18053dd5c225SAndreas Gohr * used in internalmedia() and externalmedia() 1806b739ff0fSPierre Spring * 1807b739ff0fSPierre Spring * @author Pierre Spring <pierre.spring@liip.ch> 18083dd5c225SAndreas Gohr * @param string $src media ID 18093dd5c225SAndreas Gohr * @param string $title descriptive text 18103dd5c225SAndreas Gohr * @param string $align left|center|right 18113dd5c225SAndreas Gohr * @param int $width width of media in pixel 18123dd5c225SAndreas Gohr * @param int $height height of media in pixel 18133dd5c225SAndreas Gohr * @param string $cache cache|recache|nocache 18143dd5c225SAndreas Gohr * @param bool $render should the media be embedded inline or just linked 18153dd5c225SAndreas Gohr * @return array associative array with link config 1816b739ff0fSPierre Spring */ 1817de369923SAndreas Gohr public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { 1818b739ff0fSPierre Spring global $conf; 1819b739ff0fSPierre Spring 1820b739ff0fSPierre Spring $link = array(); 1821b739ff0fSPierre Spring $link['class'] = 'media'; 1822b739ff0fSPierre Spring $link['style'] = ''; 1823b739ff0fSPierre Spring $link['pre'] = ''; 1824b739ff0fSPierre Spring $link['suf'] = ''; 1825b739ff0fSPierre Spring $link['more'] = ''; 1826b739ff0fSPierre Spring $link['target'] = $conf['target']['media']; 1827bc3d2252SAndreas Gohr if($conf['target']['media']) $link['rel'] = 'noopener'; 1828b739ff0fSPierre Spring $link['title'] = $this->_xmlEntities($src); 1829b739ff0fSPierre Spring $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1830b739ff0fSPierre Spring 1831b739ff0fSPierre Spring return $link; 1832b739ff0fSPierre Spring } 183391459163SAnika Henke 18342a2a2ba2SAnika Henke /** 18352a2a2ba2SAnika Henke * Embed video(s) in HTML 18362a2a2ba2SAnika Henke * 18372a2a2ba2SAnika Henke * @author Anika Henke <anika@selfthinker.org> 18380877a1f1SSchplurtz le Déboulonné * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 18392a2a2ba2SAnika Henke * 18402a2a2ba2SAnika Henke * @param string $src - ID of video to embed 18412a2a2ba2SAnika Henke * @param int $width - width of the video in pixels 18422a2a2ba2SAnika Henke * @param int $height - height of the video in pixels 18432a2a2ba2SAnika Henke * @param array $atts - additional attributes for the <video> tag 1844f50634f0SAnika Henke * @return string 18452a2a2ba2SAnika Henke */ 1846de369923SAndreas Gohr public function _video($src, $width, $height, $atts = null) { 18472a2a2ba2SAnika Henke // prepare width and height 18482a2a2ba2SAnika Henke if(is_null($atts)) $atts = array(); 18492a2a2ba2SAnika Henke $atts['width'] = (int) $width; 18502a2a2ba2SAnika Henke $atts['height'] = (int) $height; 18512a2a2ba2SAnika Henke if(!$atts['width']) $atts['width'] = 320; 18522a2a2ba2SAnika Henke if(!$atts['height']) $atts['height'] = 240; 18532a2a2ba2SAnika Henke 1854410ee62aSAnika Henke $posterUrl = ''; 1855410ee62aSAnika Henke $files = array(); 18560877a1f1SSchplurtz le Déboulonné $tracks = array(); 1857410ee62aSAnika Henke $isExternal = media_isexternal($src); 1858410ee62aSAnika Henke 1859410ee62aSAnika Henke if ($isExternal) { 1860410ee62aSAnika Henke // take direct source for external files 1861702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1862410ee62aSAnika Henke $files[$srcMime] = $src; 1863410ee62aSAnika Henke } else { 18643d7a9e0aSAnika Henke // prepare alternative formats 18653d7a9e0aSAnika Henke $extensions = array('webm', 'ogv', 'mp4'); 1866410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 186759bc3b48SGerrit Uitslag $poster = media_alternativefiles($src, array('jpg', 'png')); 18680877a1f1SSchplurtz le Déboulonné $tracks = media_trackfiles($src); 186999f943f6SAnika Henke if(!empty($poster)) { 18702d338eabSAndreas Gohr $posterUrl = ml(reset($poster), '', true, '&'); 187199f943f6SAnika Henke } 1872410ee62aSAnika Henke } 18732a2a2ba2SAnika Henke 1874f50634f0SAnika Henke $out = ''; 187579e53fe5SAnika Henke // open video tag 1876f50634f0SAnika Henke $out .= '<video '.buildAttributes($atts).' controls="controls"'; 18773641199aSAnika Henke if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"'; 1878f50634f0SAnika Henke $out .= '>'.NL; 18793641199aSAnika Henke $fallback = ''; 188079e53fe5SAnika Henke 188179e53fe5SAnika Henke // output source for each alternative video format 1882410ee62aSAnika Henke foreach($files as $mime => $file) { 1883410ee62aSAnika Henke if ($isExternal) { 1884410ee62aSAnika Henke $url = $file; 1885410ee62aSAnika Henke $linkType = 'externalmedia'; 1886410ee62aSAnika Henke } else { 18872d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1888410ee62aSAnika Henke $linkType = 'internalmedia'; 1889410ee62aSAnika Henke } 18908cbc5ee8SAndreas Gohr $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 18913d7a9e0aSAnika Henke 1892f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 189379e53fe5SAnika Henke // alternative content (just a link to the file) 189464159a61SAndreas Gohr $fallback .= $this->$linkType( 189564159a61SAndreas Gohr $file, 189664159a61SAndreas Gohr $title, 189764159a61SAndreas Gohr null, 189864159a61SAndreas Gohr null, 189964159a61SAndreas Gohr null, 190064159a61SAndreas Gohr $cache = null, 190164159a61SAndreas Gohr $linking = 'linkonly', 190264159a61SAndreas Gohr $return = true 190364159a61SAndreas Gohr ); 19043d7a9e0aSAnika Henke } 19052a2a2ba2SAnika Henke 19060877a1f1SSchplurtz le Déboulonné // output each track if any 19070877a1f1SSchplurtz le Déboulonné foreach( $tracks as $trackid => $info ) { 190823c61bbeSSchplurtz le Déboulonné list( $kind, $srclang ) = array_map( 'hsc', $info ); 190923c61bbeSSchplurtz le Déboulonné $out .= "<track kind=\"$kind\" srclang=\"$srclang\" "; 191023c61bbeSSchplurtz le Déboulonné $out .= "label=\"$srclang\" "; 19110877a1f1SSchplurtz le Déboulonné $out .= 'src="'.ml($trackid, '', true).'">'.NL; 19120877a1f1SSchplurtz le Déboulonné } 19130877a1f1SSchplurtz le Déboulonné 19142a2a2ba2SAnika Henke // finish 19153641199aSAnika Henke $out .= $fallback; 1916f50634f0SAnika Henke $out .= '</video>'.NL; 1917f50634f0SAnika Henke return $out; 19182a2a2ba2SAnika Henke } 19192a2a2ba2SAnika Henke 1920b44a5dceSAnika Henke /** 1921b44a5dceSAnika Henke * Embed audio in HTML 1922b44a5dceSAnika Henke * 1923b44a5dceSAnika Henke * @author Anika Henke <anika@selfthinker.org> 1924b44a5dceSAnika Henke * 1925b44a5dceSAnika Henke * @param string $src - ID of audio to embed 19266d4af72aSAnika Henke * @param array $atts - additional attributes for the <audio> tag 1927f50634f0SAnika Henke * @return string 1928b44a5dceSAnika Henke */ 1929de369923SAndreas Gohr public function _audio($src, $atts = array()) { 1930702e97d3SAnika Henke $files = array(); 1931410ee62aSAnika Henke $isExternal = media_isexternal($src); 1932b44a5dceSAnika Henke 1933410ee62aSAnika Henke if ($isExternal) { 1934410ee62aSAnika Henke // take direct source for external files 1935702e97d3SAnika Henke list(/*ext*/, $srcMime) = mimetype($src); 1936410ee62aSAnika Henke $files[$srcMime] = $src; 1937410ee62aSAnika Henke } else { 1938b44a5dceSAnika Henke // prepare alternative formats 1939b44a5dceSAnika Henke $extensions = array('ogg', 'mp3', 'wav'); 1940410ee62aSAnika Henke $files = media_alternativefiles($src, $extensions); 1941410ee62aSAnika Henke } 1942b44a5dceSAnika Henke 1943f50634f0SAnika Henke $out = ''; 1944b44a5dceSAnika Henke // open audio tag 1945f50634f0SAnika Henke $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL; 19463641199aSAnika Henke $fallback = ''; 1947b44a5dceSAnika Henke 1948b44a5dceSAnika Henke // output source for each alternative audio format 1949410ee62aSAnika Henke foreach($files as $mime => $file) { 1950410ee62aSAnika Henke if ($isExternal) { 1951410ee62aSAnika Henke $url = $file; 1952410ee62aSAnika Henke $linkType = 'externalmedia'; 1953410ee62aSAnika Henke } else { 19542d338eabSAndreas Gohr $url = ml($file, '', true, '&'); 1955410ee62aSAnika Henke $linkType = 'internalmedia'; 1956410ee62aSAnika Henke } 19578cbc5ee8SAndreas Gohr $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); 1958b44a5dceSAnika Henke 1959f50634f0SAnika Henke $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; 1960b44a5dceSAnika Henke // alternative content (just a link to the file) 196164159a61SAndreas Gohr $fallback .= $this->$linkType( 196264159a61SAndreas Gohr $file, 196364159a61SAndreas Gohr $title, 196464159a61SAndreas Gohr null, 196564159a61SAndreas Gohr null, 196664159a61SAndreas Gohr null, 196764159a61SAndreas Gohr $cache = null, 196864159a61SAndreas Gohr $linking = 'linkonly', 196964159a61SAndreas Gohr $return = true 197064159a61SAndreas Gohr ); 1971b44a5dceSAnika Henke } 1972b44a5dceSAnika Henke 1973b44a5dceSAnika Henke // finish 19743641199aSAnika Henke $out .= $fallback; 1975f50634f0SAnika Henke $out .= '</audio>'.NL; 1976f50634f0SAnika Henke return $out; 1977b44a5dceSAnika Henke } 1978b44a5dceSAnika Henke 19795c2eed9aSlisps /** 198052dc5eadSlisps * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media() 19815c2eed9aSlisps * which returns an existing media revision less or equal to rev or date_at 19825c2eed9aSlisps * 19835c2eed9aSlisps * @author lisps 19845c2eed9aSlisps * @param string $media_id 19855c2eed9aSlisps * @access protected 19865c2eed9aSlisps * @return string revision ('' for current) 19875c2eed9aSlisps */ 1988de369923SAndreas Gohr protected function _getLastMediaRevisionAt($media_id){ 198952dc5eadSlisps if(!$this->date_at || media_isexternal($media_id)) return ''; 199078b874e6Slisps $pagelog = new MediaChangeLog($media_id); 199178b874e6Slisps return $pagelog->getLastRevisionAt($this->date_at); 19925c2eed9aSlisps } 19935c2eed9aSlisps 19943dd5c225SAndreas Gohr #endregion 19950cecf9d5Sandi} 19960cecf9d5Sandi 1997e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 1998