12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946 |
- <?php
- use dokuwiki\ChangeLog\MediaChangeLog;
- use dokuwiki\File\MediaResolver;
- use dokuwiki\File\PageResolver;
- /**
- * Renderer for XHTML output
- *
- * This is DokuWiki's main renderer used to display page content in the wiki
- *
- * @author Harry Fuecks <hfuecks@gmail.com>
- * @author Andreas Gohr <andi@splitbrain.org>
- *
- */
- class Doku_Renderer_xhtml extends Doku_Renderer {
- /** @var array store the table of contents */
- public $toc = array();
- /** @var array A stack of section edit data */
- protected $sectionedits = array();
- /** @var int last section edit id, used by startSectionEdit */
- protected $lastsecid = 0;
- /** @var array a list of footnotes, list starts at 1! */
- protected $footnotes = array();
- /** @var int current section level */
- protected $lastlevel = 0;
- /** @var array section node tracker */
- protected $node = array(0, 0, 0, 0, 0);
- /** @var string temporary $doc store */
- protected $store = '';
- /** @var array global counter, for table classes etc. */
- protected $_counter = array(); //
- /** @var int counts the code and file blocks, used to provide download links */
- protected $_codeblock = 0;
- /** @var array list of allowed URL schemes */
- protected $schemes = null;
- /**
- * Register a new edit section range
- *
- * @param int $start The byte position for the edit start
- * @param array $data Associative array with section data:
- * Key 'name': the section name/title
- * Key 'target': the target for the section edit,
- * e.g. 'section' or 'table'
- * Key 'hid': header id
- * Key 'codeblockOffset': actual code block index
- * Key 'start': set in startSectionEdit(),
- * do not set yourself
- * Key 'range': calculated from 'start' and
- * $key in finishSectionEdit(),
- * do not set yourself
- * @return string A marker class for the starting HTML element
- *
- * @author Adrian Lang <lang@cosmocode.de>
- */
- public function startSectionEdit($start, $data) {
- if (!is_array($data)) {
- msg(
- sprintf(
- 'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.',
- hsc((string) $data)
- ), -1
- );
- // @deprecated 2018-04-14, backward compatibility
- $args = func_get_args();
- $data = array();
- if(isset($args[1])) $data['target'] = $args[1];
- if(isset($args[2])) $data['name'] = $args[2];
- if(isset($args[3])) $data['hid'] = $args[3];
- }
- $data['secid'] = ++$this->lastsecid;
- $data['start'] = $start;
- $this->sectionedits[] = $data;
- return 'sectionedit'.$data['secid'];
- }
- /**
- * Finish an edit section range
- *
- * @param int $end The byte position for the edit end; null for the rest of the page
- *
- * @author Adrian Lang <lang@cosmocode.de>
- */
- public function finishSectionEdit($end = null, $hid = null) {
- if(count($this->sectionedits) == 0) {
- return;
- }
- $data = array_pop($this->sectionedits);
- if(!is_null($end) && $end <= $data['start']) {
- return;
- }
- if(!is_null($hid)) {
- $data['hid'] .= $hid;
- }
- $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end);
- unset($data['start']);
- $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->';
- }
- /**
- * Returns the format produced by this renderer.
- *
- * @return string always 'xhtml'
- */
- public function getFormat() {
- return 'xhtml';
- }
- /**
- * Initialize the document
- */
- public function document_start() {
- //reset some internals
- $this->toc = array();
- }
- /**
- * Finalize the document
- */
- public function document_end() {
- // Finish open section edits.
- while(count($this->sectionedits) > 0) {
- if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) {
- // If there is only one section, do not write a section edit
- // marker.
- array_pop($this->sectionedits);
- } else {
- $this->finishSectionEdit();
- }
- }
- if(count($this->footnotes) > 0) {
- $this->doc .= '<div class="footnotes">'.DOKU_LF;
- foreach($this->footnotes as $id => $footnote) {
- // check its not a placeholder that indicates actual footnote text is elsewhere
- if(substr($footnote, 0, 5) != "@@FNT") {
- // open the footnote and set the anchor and backlink
- $this->doc .= '<div class="fn">';
- $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">';
- $this->doc .= $id.')</a></sup> '.DOKU_LF;
- // get any other footnotes that use the same markup
- $alt = array_keys($this->footnotes, "@@FNT$id");
- if(count($alt)) {
- foreach($alt as $ref) {
- // set anchor and backlink for the other footnotes
- $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">';
- $this->doc .= ($ref).')</a></sup> '.DOKU_LF;
- }
- }
- // add footnote markup and close this footnote
- $this->doc .= '<div class="content">'.$footnote.'</div>';
- $this->doc .= '</div>'.DOKU_LF;
- }
- }
- $this->doc .= '</div>'.DOKU_LF;
- }
- // Prepare the TOC
- global $conf;
- if(
- $this->info['toc'] &&
- is_array($this->toc) &&
- $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']
- ) {
- global $TOC;
- $TOC = $this->toc;
- }
- // make sure there are no empty paragraphs
- $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc);
- }
- /**
- * Add an item to the TOC
- *
- * @param string $id the hash link
- * @param string $text the text to display
- * @param int $level the nesting level
- */
- public function toc_additem($id, $text, $level) {
- global $conf;
- //handle TOC
- if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) {
- $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1);
- }
- }
- /**
- * Render a heading
- *
- * @param string $text the text to display
- * @param int $level header level
- * @param int $pos byte position in the original source
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function header($text, $level, $pos, $returnonly = false) {
- global $conf;
- if(blank($text)) return; //skip empty headlines
- $hid = $this->_headerToLink($text, true);
- //only add items within configured levels
- $this->toc_additem($hid, $text, $level);
- // adjust $node to reflect hierarchy of levels
- $this->node[$level - 1]++;
- if($level < $this->lastlevel) {
- for($i = 0; $i < $this->lastlevel - $level; $i++) {
- $this->node[$this->lastlevel - $i - 1] = 0;
- }
- }
- $this->lastlevel = $level;
- if($level <= $conf['maxseclevel'] &&
- count($this->sectionedits) > 0 &&
- $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section'
- ) {
- $this->finishSectionEdit($pos - 1);
- }
- // build the header
- $header = DOKU_LF.'<h'.$level;
- if($level <= $conf['maxseclevel']) {
- $data = array();
- $data['target'] = 'section';
- $data['name'] = $text;
- $data['hid'] = $hid;
- $data['codeblockOffset'] = $this->_codeblock;
- $header .= ' class="'.$this->startSectionEdit($pos, $data).'"';
- }
- $header .= ' id="'.$hid.'">';
- $header .= $this->_xmlEntities($text);
- $header .= "</h$level>".DOKU_LF;
- if ($returnonly) {
- return $header;
- } else {
- $this->doc .= $header;
- }
- }
- /**
- * Open a new section
- *
- * @param int $level section level (as determined by the previous header)
- */
- public function section_open($level) {
- $this->doc .= '<div class="level'.$level.'">'.DOKU_LF;
- }
- /**
- * Close the current section
- */
- public function section_close() {
- $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
- }
- /**
- * Render plain text data
- *
- * @param $text
- */
- public function cdata($text) {
- $this->doc .= $this->_xmlEntities($text);
- }
- /**
- * Open a paragraph
- */
- public function p_open() {
- $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
- }
- /**
- * Close a paragraph
- */
- public function p_close() {
- $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
- }
- /**
- * Create a line break
- */
- public function linebreak() {
- $this->doc .= '<br/>'.DOKU_LF;
- }
- /**
- * Create a horizontal line
- */
- public function hr() {
- $this->doc .= '<hr />'.DOKU_LF;
- }
- /**
- * Start strong (bold) formatting
- */
- public function strong_open() {
- $this->doc .= '<strong>';
- }
- /**
- * Stop strong (bold) formatting
- */
- public function strong_close() {
- $this->doc .= '</strong>';
- }
- /**
- * Start emphasis (italics) formatting
- */
- public function emphasis_open() {
- $this->doc .= '<em>';
- }
- /**
- * Stop emphasis (italics) formatting
- */
- public function emphasis_close() {
- $this->doc .= '</em>';
- }
- /**
- * Start underline formatting
- */
- public function underline_open() {
- $this->doc .= '<em class="u">';
- }
- /**
- * Stop underline formatting
- */
- public function underline_close() {
- $this->doc .= '</em>';
- }
- /**
- * Start monospace formatting
- */
- public function monospace_open() {
- $this->doc .= '<code>';
- }
- /**
- * Stop monospace formatting
- */
- public function monospace_close() {
- $this->doc .= '</code>';
- }
- /**
- * Start a subscript
- */
- public function subscript_open() {
- $this->doc .= '<sub>';
- }
- /**
- * Stop a subscript
- */
- public function subscript_close() {
- $this->doc .= '</sub>';
- }
- /**
- * Start a superscript
- */
- public function superscript_open() {
- $this->doc .= '<sup>';
- }
- /**
- * Stop a superscript
- */
- public function superscript_close() {
- $this->doc .= '</sup>';
- }
- /**
- * Start deleted (strike-through) formatting
- */
- public function deleted_open() {
- $this->doc .= '<del>';
- }
- /**
- * Stop deleted (strike-through) formatting
- */
- public function deleted_close() {
- $this->doc .= '</del>';
- }
- /**
- * Callback for footnote start syntax
- *
- * All following content will go to the footnote instead of
- * the document. To achieve this the previous rendered content
- * is moved to $store and $doc is cleared
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- */
- public function footnote_open() {
- // move current content to store and record footnote
- $this->store = $this->doc;
- $this->doc = '';
- }
- /**
- * Callback for footnote end syntax
- *
- * All rendered content is moved to the $footnotes array and the old
- * content is restored from $store again
- *
- * @author Andreas Gohr
- */
- public function footnote_close() {
- /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */
- static $fnid = 0;
- // assign new footnote id (we start at 1)
- $fnid++;
- // recover footnote into the stack and restore old content
- $footnote = $this->doc;
- $this->doc = $this->store;
- $this->store = '';
- // check to see if this footnote has been seen before
- $i = array_search($footnote, $this->footnotes);
- if($i === false) {
- // its a new footnote, add it to the $footnotes array
- $this->footnotes[$fnid] = $footnote;
- } else {
- // seen this one before, save a placeholder
- $this->footnotes[$fnid] = "@@FNT".($i);
- }
- // output the footnote reference and link
- $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>';
- }
- /**
- * Open an unordered list
- *
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function listu_open($classes = null) {
- $class = '';
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class = " class=\"$classes\"";
- }
- $this->doc .= "<ul$class>".DOKU_LF;
- }
- /**
- * Close an unordered list
- */
- public function listu_close() {
- $this->doc .= '</ul>'.DOKU_LF;
- }
- /**
- * Open an ordered list
- *
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function listo_open($classes = null) {
- $class = '';
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class = " class=\"$classes\"";
- }
- $this->doc .= "<ol$class>".DOKU_LF;
- }
- /**
- * Close an ordered list
- */
- public function listo_close() {
- $this->doc .= '</ol>'.DOKU_LF;
- }
- /**
- * Open a list item
- *
- * @param int $level the nesting level
- * @param bool $node true when a node; false when a leaf
- */
- public function listitem_open($level, $node=false) {
- $branching = $node ? ' node' : '';
- $this->doc .= '<li class="level'.$level.$branching.'">';
- }
- /**
- * Close a list item
- */
- public function listitem_close() {
- $this->doc .= '</li>'.DOKU_LF;
- }
- /**
- * Start the content of a list item
- */
- public function listcontent_open() {
- $this->doc .= '<div class="li">';
- }
- /**
- * Stop the content of a list item
- */
- public function listcontent_close() {
- $this->doc .= '</div>'.DOKU_LF;
- }
- /**
- * Output unformatted $text
- *
- * Defaults to $this->cdata()
- *
- * @param string $text
- */
- public function unformatted($text) {
- $this->doc .= $this->_xmlEntities($text);
- }
- /**
- * Start a block quote
- */
- public function quote_open() {
- $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
- }
- /**
- * Stop a block quote
- */
- public function quote_close() {
- $this->doc .= '</div></blockquote>'.DOKU_LF;
- }
- /**
- * Output preformatted text
- *
- * @param string $text
- */
- public function preformatted($text) {
- $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF;
- }
- /**
- * Display text as file content, optionally syntax highlighted
- *
- * @param string $text text to show
- * @param string $language programming language to use for syntax highlighting
- * @param string $filename file path label
- * @param array $options assoziative array with additional geshi options
- */
- public function file($text, $language = null, $filename = null, $options=null) {
- $this->_highlight('file', $text, $language, $filename, $options);
- }
- /**
- * Display text as code content, optionally syntax highlighted
- *
- * @param string $text text to show
- * @param string $language programming language to use for syntax highlighting
- * @param string $filename file path label
- * @param array $options assoziative array with additional geshi options
- */
- public function code($text, $language = null, $filename = null, $options=null) {
- $this->_highlight('code', $text, $language, $filename, $options);
- }
- /**
- * Use GeSHi to highlight language syntax in code and file blocks
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- * @param string $type code|file
- * @param string $text text to show
- * @param string $language programming language to use for syntax highlighting
- * @param string $filename file path label
- * @param array $options assoziative array with additional geshi options
- */
- public function _highlight($type, $text, $language = null, $filename = null, $options = null) {
- global $ID;
- global $lang;
- global $INPUT;
- $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language ?? '');
- if($filename) {
- // add icon
- list($ext) = mimetype($filename, false);
- $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
- $class = 'mediafile mf_'.$class;
- $offset = 0;
- if ($INPUT->has('codeblockOffset')) {
- $offset = $INPUT->str('codeblockOffset');
- }
- $this->doc .= '<dl class="'.$type.'">'.DOKU_LF;
- $this->doc .= '<dt><a href="' .
- exportlink(
- $ID,
- 'code',
- array('codeblock' => $offset + $this->_codeblock)
- ) . '" title="' . $lang['download'] . '" class="' . $class . '">';
- $this->doc .= hsc($filename);
- $this->doc .= '</a></dt>'.DOKU_LF.'<dd>';
- }
- if($text[0] == "\n") {
- $text = substr($text, 1);
- }
- if(substr($text, -1) == "\n") {
- $text = substr($text, 0, -1);
- }
- if(empty($language)) { // empty is faster than is_null and can prevent '' string
- $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
- } else {
- $class = 'code'; //we always need the code class to make the syntax highlighting apply
- if($type != 'code') $class .= ' '.$type;
- $this->doc .= "<pre class=\"$class $language\">" .
- p_xhtml_cached_geshi($text, $language, '', $options) .
- '</pre>' . DOKU_LF;
- }
- if($filename) {
- $this->doc .= '</dd></dl>'.DOKU_LF;
- }
- $this->_codeblock++;
- }
- /**
- * Format an acronym
- *
- * Uses $this->acronyms
- *
- * @param string $acronym
- */
- public function acronym($acronym) {
- if(array_key_exists($acronym, $this->acronyms)) {
- $title = $this->_xmlEntities($this->acronyms[$acronym]);
- $this->doc .= '<abbr title="'.$title
- .'">'.$this->_xmlEntities($acronym).'</abbr>';
- } else {
- $this->doc .= $this->_xmlEntities($acronym);
- }
- }
- /**
- * Format a smiley
- *
- * Uses $this->smiley
- *
- * @param string $smiley
- */
- public function smiley($smiley) {
- if (isset($this->smileys[$smiley])) {
- $this->doc .= '<img src="' . DOKU_BASE . 'lib/images/smileys/' . $this->smileys[$smiley] .
- '" class="icon smiley" alt="' . $this->_xmlEntities($smiley) . '" />';
- } else {
- $this->doc .= $this->_xmlEntities($smiley);
- }
- }
- /**
- * Format an entity
- *
- * Entities are basically small text replacements
- *
- * Uses $this->entities
- *
- * @param string $entity
- */
- public function entity($entity) {
- if(array_key_exists($entity, $this->entities)) {
- $this->doc .= $this->entities[$entity];
- } else {
- $this->doc .= $this->_xmlEntities($entity);
- }
- }
- /**
- * Typographically format a multiply sign
- *
- * Example: ($x=640, $y=480) should result in "640×480"
- *
- * @param string|int $x first value
- * @param string|int $y second value
- */
- public function multiplyentity($x, $y) {
- $this->doc .= "$x×$y";
- }
- /**
- * Render an opening single quote char (language specific)
- */
- public function singlequoteopening() {
- global $lang;
- $this->doc .= $lang['singlequoteopening'];
- }
- /**
- * Render a closing single quote char (language specific)
- */
- public function singlequoteclosing() {
- global $lang;
- $this->doc .= $lang['singlequoteclosing'];
- }
- /**
- * Render an apostrophe char (language specific)
- */
- public function apostrophe() {
- global $lang;
- $this->doc .= $lang['apostrophe'];
- }
- /**
- * Render an opening double quote char (language specific)
- */
- public function doublequoteopening() {
- global $lang;
- $this->doc .= $lang['doublequoteopening'];
- }
- /**
- * Render an closinging double quote char (language specific)
- */
- public function doublequoteclosing() {
- global $lang;
- $this->doc .= $lang['doublequoteclosing'];
- }
- /**
- * Render a CamelCase link
- *
- * @param string $link The link name
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- *
- * @see http://en.wikipedia.org/wiki/CamelCase
- */
- public function camelcaselink($link, $returnonly = false) {
- if($returnonly) {
- return $this->internallink($link, $link, null, true);
- } else {
- $this->internallink($link, $link);
- }
- }
- /**
- * Render a page local link
- *
- * @param string $hash hash link identifier
- * @param string $name name for the link
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function locallink($hash, $name = null, $returnonly = false) {
- global $ID;
- $name = $this->_getLinkTitle($name, $hash, $isImage);
- $hash = $this->_headerToLink($hash);
- $title = $ID.' ↵';
- $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
- $doc .= $name;
- $doc .= '</a>';
- if($returnonly) {
- return $doc;
- } else {
- $this->doc .= $doc;
- }
- }
- /**
- * Render an internal Wiki Link
- *
- * $search,$returnonly & $linktype are not for the renderer but are used
- * elsewhere - no need to implement them in other renderers
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- * @param string $id pageid
- * @param string|null $name link name
- * @param string|null $search adds search url param
- * @param bool $returnonly whether to return html or write to doc attribute
- * @param string $linktype type to set use of headings
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') {
- global $conf;
- global $ID;
- global $INFO;
- $params = '';
- $parts = explode('?', $id, 2);
- if(count($parts) === 2) {
- $id = $parts[0];
- $params = $parts[1];
- }
- // For empty $id we need to know the current $ID
- // We need this check because _simpleTitle needs
- // correct $id and resolve_pageid() use cleanID($id)
- // (some things could be lost)
- if($id === '') {
- $id = $ID;
- }
- // default name is based on $id as given
- $default = $this->_simpleTitle($id);
- // now first resolve and clean up the $id
- $id = (new PageResolver($ID))->resolveId($id, $this->date_at, true);
- $exists = page_exists($id, $this->date_at, false, true);
- $link = array();
- $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
- if(!$isImage) {
- if($exists) {
- $class = 'wikilink1';
- } else {
- $class = 'wikilink2';
- $link['rel'] = 'nofollow';
- }
- } else {
- $class = 'media';
- }
- //keep hash anchor
- list($id, $hash) = sexplode('#', $id, 2);
- if(!empty($hash)) $hash = $this->_headerToLink($hash);
- //prepare for formating
- $link['target'] = $conf['target']['wiki'];
- $link['style'] = '';
- $link['pre'] = '';
- $link['suf'] = '';
- $link['more'] = 'data-wiki-id="'.$id.'"'; // id is already cleaned
- $link['class'] = $class;
- if($this->date_at) {
- $params = $params.'&at='.rawurlencode($this->date_at);
- }
- $link['url'] = wl($id, $params);
- $link['name'] = $name;
- $link['title'] = $id;
- //add search string
- if($search) {
- ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&';
- if(is_array($search)) {
- $search = array_map('rawurlencode', $search);
- $link['url'] .= 's[]='.join('&s[]=', $search);
- } else {
- $link['url'] .= 's='.rawurlencode($search);
- }
- }
- //keep hash
- if($hash) $link['url'] .= '#'.$hash;
- //output formatted
- if($returnonly) {
- return $this->_formatLink($link);
- } else {
- $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Render an external link
- *
- * @param string $url full URL with scheme
- * @param string|array $name name for the link, array for media file
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function externallink($url, $name = null, $returnonly = false) {
- global $conf;
- $name = $this->_getLinkTitle($name, $url, $isImage);
- // url might be an attack vector, only allow registered protocols
- if(is_null($this->schemes)) $this->schemes = getSchemes();
- list($scheme) = explode('://', $url);
- $scheme = strtolower($scheme);
- if(!in_array($scheme, $this->schemes)) $url = '';
- // is there still an URL?
- if(!$url) {
- if($returnonly) {
- return $name;
- } else {
- $this->doc .= $name;
- }
- return;
- }
- // set class
- if(!$isImage) {
- $class = 'urlextern';
- } else {
- $class = 'media';
- }
- //prepare for formating
- $link = array();
- $link['target'] = $conf['target']['extern'];
- $link['style'] = '';
- $link['pre'] = '';
- $link['suf'] = '';
- $link['more'] = '';
- $link['class'] = $class;
- $link['url'] = $url;
- $link['rel'] = '';
- $link['name'] = $name;
- $link['title'] = $this->_xmlEntities($url);
- if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow';
- if($conf['target']['extern']) $link['rel'] .= ' noopener';
- //output formatted
- if($returnonly) {
- return $this->_formatLink($link);
- } else {
- $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Render an interwiki link
- *
- * You may want to use $this->_resolveInterWiki() here
- *
- * @param string $match original link - probably not much use
- * @param string|array $name name for the link, array for media file
- * @param string $wikiName indentifier (shortcut) for the remote wiki
- * @param string $wikiUri the fragment parsed from the original link
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) {
- global $conf;
- $link = array();
- $link['target'] = $conf['target']['interwiki'];
- $link['pre'] = '';
- $link['suf'] = '';
- $link['more'] = '';
- $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage);
- $link['rel'] = '';
- //get interwiki URL
- $exists = null;
- $url = $this->_resolveInterWiki($wikiName, $wikiUri, $exists);
- if(!$isImage) {
- $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName);
- $link['class'] = "interwiki iw_$class";
- } else {
- $link['class'] = 'media';
- }
- //do we stay at the same server? Use local target
- if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) {
- $link['target'] = $conf['target']['wiki'];
- }
- if($exists !== null && !$isImage) {
- if($exists) {
- $link['class'] .= ' wikilink1';
- } else {
- $link['class'] .= ' wikilink2';
- $link['rel'] .= ' nofollow';
- }
- }
- if($conf['target']['interwiki']) $link['rel'] .= ' noopener';
- $link['url'] = $url;
- $link['title'] = $this->_xmlEntities($link['url']);
- // output formatted
- if($returnonly) {
- if($url == '') return $link['name'];
- return $this->_formatLink($link);
- } else {
- if($url == '') $this->doc .= $link['name'];
- else $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Link to windows share
- *
- * @param string $url the link
- * @param string|array $name name for the link, array for media file
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function windowssharelink($url, $name = null, $returnonly = false) {
- global $conf;
- //simple setup
- $link = array();
- $link['target'] = $conf['target']['windows'];
- $link['pre'] = '';
- $link['suf'] = '';
- $link['style'] = '';
- $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
- if(!$isImage) {
- $link['class'] = 'windows';
- } else {
- $link['class'] = 'media';
- }
- $link['title'] = $this->_xmlEntities($url);
- $url = str_replace('\\', '/', $url);
- $url = 'file:///'.$url;
- $link['url'] = $url;
- //output formatted
- if($returnonly) {
- return $this->_formatLink($link);
- } else {
- $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Render a linked E-Mail Address
- *
- * Honors $conf['mailguard'] setting
- *
- * @param string $address Email-Address
- * @param string|array $name name for the link, array for media file
- * @param bool $returnonly whether to return html or write to doc attribute
- * @return void|string writes to doc attribute or returns html depends on $returnonly
- */
- public function emaillink($address, $name = null, $returnonly = false) {
- global $conf;
- //simple setup
- $link = array();
- $link['target'] = '';
- $link['pre'] = '';
- $link['suf'] = '';
- $link['style'] = '';
- $link['more'] = '';
- $name = $this->_getLinkTitle($name, '', $isImage);
- if(!$isImage) {
- $link['class'] = 'mail';
- } else {
- $link['class'] = 'media';
- }
- $address = $this->_xmlEntities($address);
- $address = obfuscate($address);
- $title = $address;
- if(empty($name)) {
- $name = $address;
- }
- if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
- $link['url'] = 'mailto:'.$address;
- $link['name'] = $name;
- $link['title'] = $title;
- //output formatted
- if($returnonly) {
- return $this->_formatLink($link);
- } else {
- $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Render an internal media file
- *
- * @param string $src media ID
- * @param string $title descriptive text
- * @param string $align left|center|right
- * @param int $width width of media in pixel
- * @param int $height height of media in pixel
- * @param string $cache cache|recache|nocache
- * @param string $linking linkonly|detail|nolink
- * @param bool $return return HTML instead of adding to $doc
- * @return void|string writes to doc attribute or returns html depends on $return
- */
- public function internalmedia($src, $title = null, $align = null, $width = null,
- $height = null, $cache = null, $linking = null, $return = false) {
- global $ID;
- if (strpos($src, '#') !== false) {
- list($src, $hash) = sexplode('#', $src, 2);
- }
- $src = (new MediaResolver($ID))->resolveId($src,$this->date_at,true);
- $exists = media_exists($src);
- $noLink = false;
- $render = ($linking == 'linkonly') ? false : true;
- $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
- list($ext, $mime) = mimetype($src, false);
- if(substr($mime, 0, 5) == 'image' && $render) {
- $link['url'] = ml(
- $src,
- array(
- 'id' => $ID,
- 'cache' => $cache,
- 'rev' => $this->_getLastMediaRevisionAt($src)
- ),
- ($linking == 'direct')
- );
- } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) {
- // don't link movies
- $noLink = true;
- } else {
- // add file icons
- $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
- $link['class'] .= ' mediafile mf_'.$class;
- $link['url'] = ml(
- $src,
- array(
- 'id' => $ID,
- 'cache' => $cache,
- 'rev' => $this->_getLastMediaRevisionAt($src)
- ),
- true
- );
- if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')';
- }
- if (!empty($hash)) $link['url'] .= '#'.$hash;
- //markup non existing files
- if(!$exists) {
- $link['class'] .= ' wikilink2';
- }
- //output formatted
- if($return) {
- if($linking == 'nolink' || $noLink) return $link['name'];
- else return $this->_formatLink($link);
- } else {
- if($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
- else $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Render an external media file
- *
- * @param string $src full media URL
- * @param string $title descriptive text
- * @param string $align left|center|right
- * @param int $width width of media in pixel
- * @param int $height height of media in pixel
- * @param string $cache cache|recache|nocache
- * @param string $linking linkonly|detail|nolink
- * @param bool $return return HTML instead of adding to $doc
- * @return void|string writes to doc attribute or returns html depends on $return
- */
- public function externalmedia($src, $title = null, $align = null, $width = null,
- $height = null, $cache = null, $linking = null, $return = false) {
- if(link_isinterwiki($src)){
- list($shortcut, $reference) = sexplode('>', $src, 2, '');
- $exists = null;
- $src = $this->_resolveInterWiki($shortcut, $reference, $exists);
- if($src == '' && empty($title)){
- // make sure at least something will be shown in this case
- $title = $reference;
- }
- }
- list($src, $hash) = sexplode('#', $src, 2);
- $noLink = false;
- if($src == '') {
- // only output plaintext without link if there is no src
- $noLink = true;
- }
- $render = ($linking == 'linkonly') ? false : true;
- $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
- $link['url'] = ml($src, array('cache' => $cache));
- list($ext, $mime) = mimetype($src, false);
- if(substr($mime, 0, 5) == 'image' && $render) {
- // link only jpeg images
- // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true;
- } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) {
- // don't link movies
- $noLink = true;
- } else {
- // add file icons
- $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
- $link['class'] .= ' mediafile mf_'.$class;
- }
- if($hash) $link['url'] .= '#'.$hash;
- //output formatted
- if($return) {
- if($linking == 'nolink' || $noLink) return $link['name'];
- else return $this->_formatLink($link);
- } else {
- if($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
- else $this->doc .= $this->_formatLink($link);
- }
- }
- /**
- * Renders an RSS feed
- *
- * @param string $url URL of the feed
- * @param array $params Finetuning of the output
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- */
- public function rss($url, $params) {
- global $lang;
- global $conf;
- require_once(DOKU_INC.'inc/FeedParser.php');
- $feed = new FeedParser();
- $feed->set_feed_url($url);
- //disable warning while fetching
- if(!defined('DOKU_E_LEVEL')) {
- $elvl = error_reporting(E_ERROR);
- }
- $rc = $feed->init();
- if(isset($elvl)) {
- error_reporting($elvl);
- }
- if($params['nosort']) $feed->enable_order_by_date(false);
- //decide on start and end
- if($params['reverse']) {
- $mod = -1;
- $start = $feed->get_item_quantity() - 1;
- $end = $start - ($params['max']);
- $end = ($end < -1) ? -1 : $end;
- } else {
- $mod = 1;
- $start = 0;
- $end = $feed->get_item_quantity();
- $end = ($end > $params['max']) ? $params['max'] : $end;
- }
- $this->doc .= '<ul class="rss">';
- if($rc) {
- for($x = $start; $x != $end; $x += $mod) {
- $item = $feed->get_item($x);
- $this->doc .= '<li><div class="li">';
- $lnkurl = $item->get_permalink();
- $title = html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8');
- // support feeds without links
- if($lnkurl) {
- $this->externallink($item->get_permalink(), $title);
- } else {
- $this->doc .= ' '.hsc($item->get_title());
- }
- if($params['author']) {
- $author = $item->get_author(0);
- if($author) {
- $name = $author->get_name();
- if(!$name) $name = $author->get_email();
- if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name);
- }
- }
- if($params['date']) {
- $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
- }
- if($params['details']) {
- $desc = $item->get_description();
- $desc = strip_tags($desc);
- $desc = html_entity_decode($desc, ENT_QUOTES, 'UTF-8');
- $this->doc .= '<div class="detail">';
- $this->doc .= hsc($desc);
- $this->doc .= '</div>';
- }
- $this->doc .= '</div></li>';
- }
- } else {
- $this->doc .= '<li><div class="li">';
- $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
- $this->externallink($url);
- if($conf['allowdebug']) {
- $this->doc .= '<!--'.hsc($feed->error).'-->';
- }
- $this->doc .= '</div></li>';
- }
- $this->doc .= '</ul>';
- }
- /**
- * Start a table
- *
- * @param int $maxcols maximum number of columns
- * @param int $numrows NOT IMPLEMENTED
- * @param int $pos byte position in the original source
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) {
- // initialize the row counter used for classes
- $this->_counter['row_counter'] = 0;
- $class = 'table';
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class .= ' ' . $classes;
- }
- if($pos !== null) {
- $hid = $this->_headerToLink($class, true);
- $data = array();
- $data['target'] = 'table';
- $data['name'] = '';
- $data['hid'] = $hid;
- $class .= ' '.$this->startSectionEdit($pos, $data);
- }
- $this->doc .= '<div class="'.$class.'"><table class="inline">'.
- DOKU_LF;
- }
- /**
- * Close a table
- *
- * @param int $pos byte position in the original source
- */
- public function table_close($pos = null) {
- $this->doc .= '</table></div>'.DOKU_LF;
- if($pos !== null) {
- $this->finishSectionEdit($pos);
- }
- }
- /**
- * Open a table header
- */
- public function tablethead_open() {
- $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF;
- }
- /**
- * Close a table header
- */
- public function tablethead_close() {
- $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF;
- }
- /**
- * Open a table body
- */
- public function tabletbody_open() {
- $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF;
- }
- /**
- * Close a table body
- */
- public function tabletbody_close() {
- $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF;
- }
- /**
- * Open a table footer
- */
- public function tabletfoot_open() {
- $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF;
- }
- /**
- * Close a table footer
- */
- public function tabletfoot_close() {
- $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF;
- }
- /**
- * Open a table row
- *
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function tablerow_open($classes = null) {
- // initialize the cell counter used for classes
- $this->_counter['cell_counter'] = 0;
- $class = 'row'.$this->_counter['row_counter']++;
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class .= ' ' . $classes;
- }
- $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB;
- }
- /**
- * Close a table row
- */
- public function tablerow_close() {
- $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF;
- }
- /**
- * Open a table header cell
- *
- * @param int $colspan
- * @param string $align left|center|right
- * @param int $rowspan
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) {
- $class = 'class="col'.$this->_counter['cell_counter']++;
- if(!is_null($align)) {
- $class .= ' '.$align.'align';
- }
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class .= ' ' . $classes;
- }
- $class .= '"';
- $this->doc .= '<th '.$class;
- if($colspan > 1) {
- $this->_counter['cell_counter'] += $colspan - 1;
- $this->doc .= ' colspan="'.$colspan.'"';
- }
- if($rowspan > 1) {
- $this->doc .= ' rowspan="'.$rowspan.'"';
- }
- $this->doc .= '>';
- }
- /**
- * Close a table header cell
- */
- public function tableheader_close() {
- $this->doc .= '</th>';
- }
- /**
- * Open a table cell
- *
- * @param int $colspan
- * @param string $align left|center|right
- * @param int $rowspan
- * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
- */
- public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) {
- $class = 'class="col'.$this->_counter['cell_counter']++;
- if(!is_null($align)) {
- $class .= ' '.$align.'align';
- }
- if($classes !== null) {
- if(is_array($classes)) $classes = join(' ', $classes);
- $class .= ' ' . $classes;
- }
- $class .= '"';
- $this->doc .= '<td '.$class;
- if($colspan > 1) {
- $this->_counter['cell_counter'] += $colspan - 1;
- $this->doc .= ' colspan="'.$colspan.'"';
- }
- if($rowspan > 1) {
- $this->doc .= ' rowspan="'.$rowspan.'"';
- }
- $this->doc .= '>';
- }
- /**
- * Close a table cell
- */
- public function tablecell_close() {
- $this->doc .= '</td>';
- }
- /**
- * Returns the current header level.
- * (required e.g. by the filelist plugin)
- *
- * @return int The current header level
- */
- public function getLastlevel() {
- return $this->lastlevel;
- }
- #region Utility functions
- /**
- * Build a link
- *
- * Assembles all parts defined in $link returns HTML for the link
- *
- * @param array $link attributes of a link
- * @return string
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- */
- public function _formatLink($link) {
- //make sure the url is XHTML compliant (skip mailto)
- if(substr($link['url'], 0, 7) != 'mailto:') {
- $link['url'] = str_replace('&', '&', $link['url']);
- $link['url'] = str_replace('&amp;', '&', $link['url']);
- }
- //remove double encodings in titles
- $link['title'] = str_replace('&amp;', '&', $link['title']);
- // be sure there are no bad chars in url or title
- // (we can't do this for name because it can contain an img tag)
- $link['url'] = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22'));
- $link['title'] = strtr($link['title'], array('>' => '>', '<' => '<', '"' => '"'));
- $ret = '';
- $ret .= $link['pre'];
- $ret .= '<a href="'.$link['url'].'"';
- if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"';
- if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"';
- if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"';
- if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"';
- if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"';
- if(!empty($link['more'])) $ret .= ' '.$link['more'];
- $ret .= '>';
- $ret .= $link['name'];
- $ret .= '</a>';
- $ret .= $link['suf'];
- return $ret;
- }
- /**
- * Renders internal and external media
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- * @param string $src media ID
- * @param string $title descriptive text
- * @param string $align left|center|right
- * @param int $width width of media in pixel
- * @param int $height height of media in pixel
- * @param string $cache cache|recache|nocache
- * @param bool $render should the media be embedded inline or just linked
- * @return string
- */
- public function _media($src, $title = null, $align = null, $width = null,
- $height = null, $cache = null, $render = true) {
- $ret = '';
- list($ext, $mime) = mimetype($src);
- if(substr($mime, 0, 5) == 'image') {
- // first get the $title
- if(!is_null($title)) {
- $title = $this->_xmlEntities($title);
- } elseif($ext == 'jpg' || $ext == 'jpeg') {
- //try to use the caption from IPTC/EXIF
- require_once(DOKU_INC.'inc/JpegMeta.php');
- $jpeg = new JpegMeta(mediaFN($src));
- if($jpeg !== false) $cap = $jpeg->getTitle();
- if(!empty($cap)) {
- $title = $this->_xmlEntities($cap);
- }
- }
- if(!$render) {
- // if the picture is not supposed to be rendered
- // return the title of the picture
- if($title === null || $title === "") {
- // just show the sourcename
- $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src)));
- }
- return $title;
- }
- //add image tag
- $ret .= '<img src="' . ml(
- $src,
- array(
- 'w' => $width, 'h' => $height,
- 'cache' => $cache,
- 'rev' => $this->_getLastMediaRevisionAt($src)
- )
- ) . '"';
- $ret .= ' class="media'.$align.'"';
- $ret .= ' loading="lazy"';
- if($title) {
- $ret .= ' title="'.$title.'"';
- $ret .= ' alt="'.$title.'"';
- } else {
- $ret .= ' alt=""';
- }
- if(!is_null($width))
- $ret .= ' width="'.$this->_xmlEntities($width).'"';
- if(!is_null($height))
- $ret .= ' height="'.$this->_xmlEntities($height).'"';
- $ret .= ' />';
- } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) {
- // first get the $title
- $title = !is_null($title) ? $title : false;
- if(!$render) {
- // if the file is not supposed to be rendered
- // return the title of the file (just the sourcename if there is no title)
- return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src)));
- }
- $att = array();
- $att['class'] = "media$align";
- if($title) {
- $att['title'] = $title;
- }
- if(media_supportedav($mime, 'video')) {
- //add video
- $ret .= $this->_video($src, $width, $height, $att);
- }
- if(media_supportedav($mime, 'audio')) {
- //add audio
- $ret .= $this->_audio($src, $att);
- }
- } elseif($mime == 'application/x-shockwave-flash') {
- if(!$render) {
- // if the flash is not supposed to be rendered
- // return the title of the flash
- if(!$title) {
- // just show the sourcename
- $title = \dokuwiki\Utf8\PhpString::basename(noNS($src));
- }
- return $this->_xmlEntities($title);
- }
- $att = array();
- $att['class'] = "media$align";
- if($align == 'right') $att['align'] = 'right';
- if($align == 'left') $att['align'] = 'left';
- $ret .= html_flashobject(
- ml($src, array('cache' => $cache), true, '&'), $width, $height,
- array('quality' => 'high'),
- null,
- $att,
- $this->_xmlEntities($title)
- );
- } elseif($title) {
- // well at least we have a title to display
- $ret .= $this->_xmlEntities($title);
- } else {
- // just show the sourcename
- $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src)));
- }
- return $ret;
- }
- /**
- * Escape string for output
- *
- * @param $string
- * @return string
- */
- public function _xmlEntities($string) {
- return hsc($string);
- }
- /**
- * Construct a title and handle images in titles
- *
- * @author Harry Fuecks <hfuecks@gmail.com>
- * @param string|array $title either string title or media array
- * @param string $default default title if nothing else is found
- * @param bool $isImage will be set to true if it's a media file
- * @param null|string $id linked page id (used to extract title from first heading)
- * @param string $linktype content|navigation
- * @return string HTML of the title, might be full image tag or just escaped text
- */
- public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') {
- $isImage = false;
- if(is_array($title)) {
- $isImage = true;
- return $this->_imageTitle($title);
- } elseif(is_null($title) || trim($title) == '') {
- if(useHeading($linktype) && $id) {
- $heading = p_get_first_heading($id);
- if(!blank($heading)) {
- return $this->_xmlEntities($heading);
- }
- }
- return $this->_xmlEntities($default);
- } else {
- return $this->_xmlEntities($title);
- }
- }
- /**
- * Returns HTML code for images used in link titles
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- * @param array $img
- * @return string HTML img tag or similar
- */
- public function _imageTitle($img) {
- global $ID;
- // some fixes on $img['src']
- // see internalmedia() and externalmedia()
- list($img['src']) = explode('#', $img['src'], 2);
- if($img['type'] == 'internalmedia') {
- $img['src'] = (new MediaResolver($ID))->resolveId($img['src'], $this->date_at, true);
- }
- return $this->_media(
- $img['src'],
- $img['title'],
- $img['align'],
- $img['width'],
- $img['height'],
- $img['cache']
- );
- }
- /**
- * helperfunction to return a basic link to a media
- *
- * used in internalmedia() and externalmedia()
- *
- * @author Pierre Spring <pierre.spring@liip.ch>
- * @param string $src media ID
- * @param string $title descriptive text
- * @param string $align left|center|right
- * @param int $width width of media in pixel
- * @param int $height height of media in pixel
- * @param string $cache cache|recache|nocache
- * @param bool $render should the media be embedded inline or just linked
- * @return array associative array with link config
- */
- public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) {
- global $conf;
- $link = array();
- $link['class'] = 'media';
- $link['style'] = '';
- $link['pre'] = '';
- $link['suf'] = '';
- $link['more'] = '';
- $link['target'] = $conf['target']['media'];
- if($conf['target']['media']) $link['rel'] = 'noopener';
- $link['title'] = $this->_xmlEntities($src);
- $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render);
- return $link;
- }
- /**
- * Embed video(s) in HTML
- *
- * @author Anika Henke <anika@selfthinker.org>
- * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
- *
- * @param string $src - ID of video to embed
- * @param int $width - width of the video in pixels
- * @param int $height - height of the video in pixels
- * @param array $atts - additional attributes for the <video> tag
- * @return string
- */
- public function _video($src, $width, $height, $atts = null) {
- // prepare width and height
- if(is_null($atts)) $atts = array();
- $atts['width'] = (int) $width;
- $atts['height'] = (int) $height;
- if(!$atts['width']) $atts['width'] = 320;
- if(!$atts['height']) $atts['height'] = 240;
- $posterUrl = '';
- $files = array();
- $tracks = array();
- $isExternal = media_isexternal($src);
- if ($isExternal) {
- // take direct source for external files
- list(/*ext*/, $srcMime) = mimetype($src);
- $files[$srcMime] = $src;
- } else {
- // prepare alternative formats
- $extensions = array('webm', 'ogv', 'mp4');
- $files = media_alternativefiles($src, $extensions);
- $poster = media_alternativefiles($src, array('jpg', 'png'));
- $tracks = media_trackfiles($src);
- if(!empty($poster)) {
- $posterUrl = ml(reset($poster), '', true, '&');
- }
- }
- $out = '';
- // open video tag
- $out .= '<video '.buildAttributes($atts).' controls="controls"';
- if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"';
- $out .= '>'.NL;
- $fallback = '';
- // output source for each alternative video format
- foreach($files as $mime => $file) {
- if ($isExternal) {
- $url = $file;
- $linkType = 'externalmedia';
- } else {
- $url = ml($file, '', true, '&');
- $linkType = 'internalmedia';
- }
- $title = !empty($atts['title'])
- ? $atts['title']
- : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file)));
- $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL;
- // alternative content (just a link to the file)
- $fallback .= $this->$linkType(
- $file,
- $title,
- null,
- null,
- null,
- $cache = null,
- $linking = 'linkonly',
- $return = true
- );
- }
- // output each track if any
- foreach( $tracks as $trackid => $info ) {
- list( $kind, $srclang ) = array_map( 'hsc', $info );
- $out .= "<track kind=\"$kind\" srclang=\"$srclang\" ";
- $out .= "label=\"$srclang\" ";
- $out .= 'src="'.ml($trackid, '', true).'">'.NL;
- }
- // finish
- $out .= $fallback;
- $out .= '</video>'.NL;
- return $out;
- }
- /**
- * Embed audio in HTML
- *
- * @author Anika Henke <anika@selfthinker.org>
- *
- * @param string $src - ID of audio to embed
- * @param array $atts - additional attributes for the <audio> tag
- * @return string
- */
- public function _audio($src, $atts = array()) {
- $files = array();
- $isExternal = media_isexternal($src);
- if ($isExternal) {
- // take direct source for external files
- list(/*ext*/, $srcMime) = mimetype($src);
- $files[$srcMime] = $src;
- } else {
- // prepare alternative formats
- $extensions = array('ogg', 'mp3', 'wav');
- $files = media_alternativefiles($src, $extensions);
- }
- $out = '';
- // open audio tag
- $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL;
- $fallback = '';
- // output source for each alternative audio format
- foreach($files as $mime => $file) {
- if ($isExternal) {
- $url = $file;
- $linkType = 'externalmedia';
- } else {
- $url = ml($file, '', true, '&');
- $linkType = 'internalmedia';
- }
- $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file)));
- $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL;
- // alternative content (just a link to the file)
- $fallback .= $this->$linkType(
- $file,
- $title,
- null,
- null,
- null,
- $cache = null,
- $linking = 'linkonly',
- $return = true
- );
- }
- // finish
- $out .= $fallback;
- $out .= '</audio>'.NL;
- return $out;
- }
- /**
- * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media()
- * which returns an existing media revision less or equal to rev or date_at
- *
- * @author lisps
- * @param string $media_id
- * @access protected
- * @return string revision ('' for current)
- */
- protected function _getLastMediaRevisionAt($media_id) {
- if (!$this->date_at || media_isexternal($media_id)) return '';
- $changelog = new MediaChangeLog($media_id);
- return $changelog->getLastRevisionAt($this->date_at);
- }
- #endregion
- }
- //Setup VIM: ex: et ts=4 :
|