Newer
Older
/**
* Renderer for XHTML output
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @author Andreas Gohr <andi@splitbrain.org>
*/
if ( !defined('DOKU_LF') ) {
// Some whitespace to help View > Source
define ('DOKU_LF',"\n");
}
if ( !defined('DOKU_TAB') ) {
// Some whitespace to help View > Source
define ('DOKU_TAB',"\t");
}
require_once DOKU_INC . 'inc/parser/renderer.php';
require_once DOKU_INC . 'inc/html.php';
// @access public
var $doc = ''; // will contain the whole document
var $toc = array(); // will contain the Table of Contents
var $sectionedits = array(); // A stack of section edit data
private $lastsecid = 0; // last section edit id, used by startSectionEdit
var $headers = array();
var $footnotes = array();
Anika Henke
committed
var $lastlevel = 0;
var $node = array(0,0,0,0,0);
var $_counter = array(); // used as global counter, introduced for table classes
var $_codeblock = 0; // counts the code and file blocks, used to provide download links
/**
* Register a new edit section range
*
* @param $type string The section type identifier
* @param $title string The section title
* @param $start int The byte position for the edit start
* @return string A marker class for the starting HTML element
* @author Adrian Lang <lang@cosmocode.de>
*/
public function startSectionEdit($start, $type, $title = null) {
$this->sectionedits[] = array(++$this->lastsecid, $start, $type, $title);
return 'sectionedit' . $this->lastsecid;
}
/**
* Finish an edit section range
*
* @param $end int 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) {
list($id, $start, $type, $title) = array_pop($this->sectionedits);
if (!is_null($end) && $end <= $start) {
$this->doc .= "<!-- EDIT$id " . strtoupper($type) . ' ';
if (!is_null($title)) {
$this->doc .= '"' . str_replace('"', '', $title) . '" ';
}
$this->doc .= "[$start-" . (is_null($end) ? '' : $end) . '] -->';
function getFormat(){
return 'xhtml';
}
//reset some internals
$this->toc = array();
$this->headers = array();
// Finish open section edits.
while (count($this->sectionedits) > 0) {
if ($this->sectionedits[count($this->sectionedits) - 1][1] <= 1) {
// If there is only one section, do not write a section edit
// marker.
array_pop($this->sectionedits);
} else {
if ( count ($this->footnotes) > 0 ) {
$this->doc .= '<div class="footnotes">'.DOKU_LF;
$id = 0;
foreach ( $this->footnotes as $footnote ) {
$id++; // the number of the current 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+1).'" id="fn__'.($ref+1).'" class="fn_bot">';
$this->doc .= ($ref+1).')</a></sup> '.DOKU_LF;
// add footnote markup and close this footnote
$this->doc .= $footnote;
$this->doc .= '</div>' . DOKU_LF;
}
$this->doc .= '</div>'.DOKU_LF;
Anika Henke
committed
global $conf;
if($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']){
// make sure there are no empty paragraphs
$this->doc = preg_replace('#<p>\s*</p>#','',$this->doc);
function toc_additem($id, $text, $level) {
//handle TOC
if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){
$this->toc[] = html_mktocitem($id, $text, $level-$conf['toptoclevel']+1);
Anika Henke
committed
}
function header($text, $level, $pos) {
$hid = $this->_headerToLink($text,true);
//only add items within configured levels
$this->toc_additem($hid, $text, $level);
Anika Henke
committed
// 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][2] === 'section') {
$this->finishSectionEdit($pos - 1);
$this->doc .= DOKU_LF.'<h'.$level;
if ($level <= $conf['maxseclevel']) {
$this->doc .= ' class="' . $this->startSectionEdit($pos, 'section', $text) . '"';
}
$this->doc .= ' id="'.$hid.'">';
$this->doc .= $this->_xmlEntities($text);
$this->doc .= "</h$level>".DOKU_LF;
function section_open($level) {
$this->doc .= '<div class="level' . $level . '">' . DOKU_LF;
$this->doc .= DOKU_LF.'</div>'.DOKU_LF;
$this->doc .= $this->_xmlEntities($text);
$this->doc .= '<br/>'.DOKU_LF;
$this->doc .= '<strong>';
$this->doc .= '</strong>';
$this->doc .= '<em>';
function emphasis_close() {
$this->doc .= '</em>';
function underline_open() {
function underline_close() {
function monospace_open() {
$this->doc .= '<code>';
function monospace_close() {
$this->doc .= '</code>';
function subscript_open() {
$this->doc .= '<sub>';
function subscript_close() {
$this->doc .= '</sub>';
function superscript_open() {
$this->doc .= '<sup>';
function superscript_close() {
$this->doc .= '</sup>';
$this->doc .= '<del>';
$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>
*/
// 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
*/
function footnote_close() {
// recover footnote into the stack and restore old content
$footnote = $this->doc;
// 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
$id = count($this->footnotes)+1;
$this->footnotes[count($this->footnotes)] = $footnote;
} else {
// seen this one before, translate the index to an id and save a placeholder
$i++;
$id = count($this->footnotes)+1;
$this->footnotes[count($this->footnotes)] = "@@FNT".($i);
}
$this->doc .= '<sup><a href="#fn__'.$id.'" id="fnt__'.$id.'" class="fn_top">'.$id.')</a></sup>';
$this->doc .= '<ul>'.DOKU_LF;
$this->doc .= '</ul>'.DOKU_LF;
$this->doc .= '<ol>'.DOKU_LF;
$this->doc .= '</ol>'.DOKU_LF;
function listitem_open($level) {
function listitem_close() {
$this->doc .= '</li>'.DOKU_LF;
function listcontent_open() {
$this->doc .= '<div class="li">';
function listcontent_close() {
function unformatted($text) {
$this->doc .= $this->_xmlEntities($text);
* @param string $text PHP code that is either executed or printed
* @param string $wrapper html element to wrap result if $conf['phpok'] is okff
*
if($conf['phpok']){
ob_start();
eval($text);
$this->doc .= ob_get_contents();
ob_end_clean();
} else {
$this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper);
Anika Henke
committed
function phpblock($text) {
Anika Henke
committed
}
* @param string $text html text
* @param string $wrapper html element to wrap result if $conf['htmlok'] is okff
*
if($conf['htmlok']){
$this->doc .= $text;
} else {
$this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper);
Anika Henke
committed
function htmlblock($text) {
Anika Henke
committed
}
$this->doc .= '<blockquote><div class="no">'.DOKU_LF;
$this->doc .= '</div></blockquote>'.DOKU_LF;
$this->doc .= '<pre class="code">' . trim($this->_xmlEntities($text),"\n\r") . '</pre>'. DOKU_LF;
}
function file($text, $language=null, $filename=null) {
$this->_highlight('file',$text,$language,$filename);
}
function code($text, $language=null, $filename=null) {
$this->_highlight('code',$text,$language,$filename);
}
* Use GeSHi to highlight language syntax in code and file blocks
function _highlight($type, $text, $language=null, $filename=null) {
global $ID;
global $lang;
if($filename){
list($ext) = mimetype($filename,false);
$class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
$class = 'mediafile mf_'.$class;
$this->doc .= '<dl class="'.$type.'">'.DOKU_LF;
$this->doc .= '<dt><a href="'.exportlink($ID,'code',array('codeblock'=>$this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">';
$this->doc .= hsc($filename);
$this->doc .= '</a></dt>'.DOKU_LF.'<dd>';
}
Gina Haeussge
committed
if ($text{0} == "\n") {
$text = substr($text, 1);
}
if (substr($text, -1) == "\n") {
$text = substr($text, 0, -1);
}
if ( is_null($language) ) {
$this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
$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, '').'</pre>'.DOKU_LF;
if($filename){
$this->doc .= '</dd></dl>'.DOKU_LF;
}
$this->_codeblock++;
function acronym($acronym) {
if ( array_key_exists($acronym, $this->acronyms) ) {
$this->doc .= '<abbr title="'.$title
.'">'.$this->_xmlEntities($acronym).'</abbr>';
$this->doc .= $this->_xmlEntities($acronym);
function smiley($smiley) {
if ( array_key_exists($smiley, $this->smileys) ) {
$this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley].
$this->doc .= $this->_xmlEntities($smiley);
function wordblock($word) {
if ( array_key_exists($word, $this->badwords) ) {
$this->doc .= '** BLEEP **';
$this->doc .= $this->_xmlEntities($word);
function entity($entity) {
if ( array_key_exists($entity, $this->entities) ) {
$this->doc .= $this->entities[$entity];
$this->doc .= $this->_xmlEntities($entity);
function multiplyentity($x, $y) {
$this->doc .= "$x×$y";
function singlequoteopening() {
global $lang;
$this->doc .= $lang['singlequoteopening'];
function singlequoteclosing() {
global $lang;
$this->doc .= $lang['singlequoteclosing'];
function apostrophe() {
global $lang;
$this->doc .= $lang['apostrophe'];
function doublequoteopening() {
global $lang;
$this->doc .= $lang['doublequoteopening'];
function doublequoteclosing() {
global $lang;
$this->doc .= $lang['doublequoteclosing'];
/**
*/
function camelcaselink($link) {
function locallink($hash, $name = NULL){
global $ID;
$name = $this->_getLinkTitle($name, $hash, $isImage);
$hash = $this->_headerToLink($hash);
$title = $ID.' ↵';
$this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
$this->doc .= $name;
$this->doc .= '</a>';
}
Chris Smith
committed
* $search,$returnonly & $linktype are not for the renderer but are used
* elsewhere - no need to implement them in other renderers
Chris Smith
committed
function internallink($id, $name = NULL, $search=NULL,$returnonly=false,$linktype='content') {
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
resolve_pageid(getNS($ID),$id,$exists);
Chris Smith
committed
$name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
if(!empty($hash)) $hash = $this->_headerToLink($hash);
$link['target'] = $conf['target']['wiki'];
$link['style'] = '';
$link['pre'] = '';
$link['suf'] = '';
if ($id == $INFO['id']) {
$link['pre'] = '<span class="curid">';
$link['suf'] = '</span>';
}
$link['url'] = wl($id, $params);
$link['name'] = $name;
$link['title'] = $id;
($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;
if($returnonly){
return $this->_formatLink($link);
}else{
$this->doc .= $this->_formatLink($link);
function externallink($url, $name = NULL) {
global $conf;
// 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){
$this->doc .= $name;
return;
}
// set class
//prepare for formating
$link['target'] = $conf['target']['extern'];
$link['style'] = '';
$link['pre'] = '';
$link['suf'] = '';
$link['class'] = $class;
$link['url'] = $url;
if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
//output formatted
$this->doc .= $this->_formatLink($link);
function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
$link = array();
$link['target'] = $conf['target']['interwiki'];
$link['pre'] = '';
$link['suf'] = '';
$link['name'] = $this->_getLinkTitle($name, $wikiUri, $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 ){
$link['target'] = $conf['target']['wiki'];
$link['url'] = $url;
$link['title'] = htmlspecialchars($link['url']);
//output formatted
$this->doc .= $this->_formatLink($link);
function windowssharelink($url, $name = NULL) {
global $conf;
global $lang;
//simple setup
$link['target'] = $conf['target']['windows'];
$link['pre'] = '';
$link['suf'] = '';
$link['style'] = '';
$link['class'] = 'windows';
$link['class'] = 'media';
$url = str_replace('\\','/',$url);
$url = 'file:///'.$url;
$link['url'] = $url;
//output formatted
$this->doc .= $this->_formatLink($link);
function emaillink($address, $name = NULL) {
global $conf;
//simple setup
$link = array();
$link['target'] = '';
$link['pre'] = '';
$link['suf'] = '';
$link['style'] = '';
$link['more'] = '';
$name = $this->_getLinkTitle($name, '', $isImage);
Esther Brunner
committed
$address = obfuscate($address);
$title = $address;
Esther Brunner
committed
if(empty($name)){
$name = $address;
if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
$link['url'] = 'mailto:'.$address;
$link['name'] = $name;
$link['title'] = $title;
//output formatted
$this->doc .= $this->_formatLink($link);
function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
$height=NULL, $cache=NULL, $linking=NULL) {
list($src,$hash) = explode('#',$src,2);
resolve_mediaid(getNS($ID),$src, $exists);
$render = ($linking == 'linkonly') ? false : true;
$link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
list($ext,$mime,$dl) = mimetype($src,false);
if(substr($mime,0,5) == 'image' && $render){
$link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct'));
}elseif(($mime == 'application/x-shockwave-flash' || substr($mime,0,5) == 'video') && $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),true);
if ($exists) $link['title'] .= ' (' . filesize_h(filesize(mediaFN($src))).')';
if($hash) $link['url'] .= '#'.$hash;
Kate Arzamastseva
committed
if (!$exists) {
$link['class'] .= ' wikilink2';
}
//output formatted
if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
else $this->doc .= $this->_formatLink($link);
function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
$height=NULL, $cache=NULL, $linking=NULL) {
list($src,$hash) = explode('#',$src,2);
$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,$dl) = mimetype($src,false);
// link only jpeg images
// if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true;
}elseif(($mime == 'application/x-shockwave-flash' || substr($mime,0,5) == 'video') && $render){
// don't link movies
// add file icons
$class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
$link['class'] .= ' mediafile mf_'.$class;
}
if($hash) $link['url'] .= '#'.$hash;
if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
else $this->doc .= $this->_formatLink($link);
* @author Andreas Gohr <andi@splitbrain.org>
*/
global $conf;
require_once(DOKU_INC.'inc/FeedParser.php');
$feed = new FeedParser();
if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
//decide on start and end
if($params['reverse']){
$mod = -1;
$start = $feed->get_item_quantity()-1;
$end = $start - ($params['max']);
}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) {
// support feeds without links
$lnkurl = $item->get_permalink();
if($lnkurl){
// title is escaped by SimplePie, we unescape here because it
// is escaped again in externallink() FS#1705
$this->externallink($item->get_permalink(),
html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8'));
}else{
$this->doc .= ' '.$item->get_title();
}
$author = $item->get_author(0);
if($author){
$name = $author->get_name();
if(!$name) $name = $author->get_email();
if($name) $this->doc .= ' '.$lang['by'].' '.$name;
}
$this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
$this->doc .= strip_tags($item->get_description());
}
$this->doc .= '</div>';
}
$this->doc .= '</div></li>';
$this->doc .= '<em>'.$lang['rssfailed'].'</em>';
if($conf['allowdebug']){
$this->doc .= '<!--'.hsc($feed->error).'-->';
}
$this->doc .= '</ul>';
// $numrows not yet implemented
function table_open($maxcols = null, $numrows = null, $pos = null){
// initialize the row counter used for classes
$this->_counter['row_counter'] = 0;
$class = 'table';
if ($pos !== null) {
$class .= ' ' . $this->startSectionEdit($pos, 'table');
}
$this->doc .= '<div class="' . $class . '"><table class="inline">' .
DOKU_LF;
function table_close($pos = null){
Anika Henke
committed
$this->doc .= '</table></div>'.DOKU_LF;
if ($pos !== null) {
$this->finishSectionEdit($pos);
}
// initialize the cell counter used for classes
$this->_counter['cell_counter'] = 0;
$class = 'row' . $this->_counter['row_counter']++;
$this->doc .= DOKU_TAB . '<tr class="'.$class.'">' . DOKU_LF . DOKU_TAB . DOKU_TAB;
$this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1){
$class = 'class="col' . $this->_counter['cell_counter']++;
$class .= '"';
$this->doc .= '<th ' . $class;
$this->_counter['cell_counter'] += $colspan-1;
$this->doc .= ' colspan="'.$colspan.'"';
if ( $rowspan > 1 ) {
$this->doc .= ' rowspan="'.$rowspan.'"';
}
$this->doc .= '>';
function tableheader_close(){
$this->doc .= '</th>';
function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1){
$class = 'class="col' . $this->_counter['cell_counter']++;
$class .= '"';
$this->doc .= '<td '.$class;
$this->_counter['cell_counter'] += $colspan-1;
$this->doc .= ' colspan="'.$colspan.'"';
if ( $rowspan > 1 ) {
$this->doc .= ' rowspan="'.$rowspan.'"';
}
$this->doc .= '>';
function tablecell_close(){