1<?php 2/** 3 * Renderer for adding Purple Numbers 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Anika Henke <anika@selfthinker.org> 7 * @author Andreas Gohr <andi@splitbrain.org> 8 */ 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12// we inherit from the XHTML renderer instead directly of the base renderer 13require_once DOKU_INC.'inc/parser/xhtml.php'; 14 15class renderer_plugin_purplenumbers extends Doku_Renderer_xhtml { 16 17 var $PNitemCount = 0; 18 19 function getFormat(){ 20 return 'xhtml'; 21 } 22 23 function canRender($format) { 24 return ($format=='xhtml'); 25 } 26 27 28 function document_end() { 29 parent::document_end(); 30 31 // make sure there are no empty paragraphs 32 $this->doc = preg_replace('#<p[^>]*>\s*<!--PN-->.*?(?:</p>)#','',$this->doc); 33 // remove PN comment again (see _getLink()) 34 $this->doc = preg_replace('/<!--PN-->/','',$this->doc); 35 } 36 37 function header($text, $level, $pos, $returnonly = false) { 38 parent::header($text, $level, $pos); 39 40 if ($this->_displayPN()) { 41 $pnid = $this->_getID($this->getConf('numbering')?2:1); 42 $linkText = $this->getConf('linkText') ? $pnid : '§'; 43 44 $link = ' <a href="#'.$pnid.'" id="'.$pnid; 45 $link .= '" class="pn" title="'.$this->getLang('sectionlink').'">'.$linkText.'</a>'; 46 $link .= $this->_getAnnotationLink(); 47 48 $this->doc = preg_replace('/(<\/h[1-5]>)$/', $link.'\\1', $this->doc); 49 } 50 } 51 52 function p_open() { 53 $eventdata = array( 54 'doc' => &$this->doc, 55 'pid' => $this->_getID() 56 ); 57 // note: this will also be triggered by empty paragraphs 58 trigger_event('PLUGIN_PURPLENUMBERS_P_OPENED', $eventdata); 59 $this->doc .= DOKU_LF.'<p'.$this->_getID(1,1).'>'.DOKU_LF; 60 } 61 62 function p_close() { 63 $this->doc .= $this->_getLink().'</p>'.DOKU_LF; 64 if (preg_match('/<p[^>]*>\s*<!--PN-->.*?(?:<\/p>)$/',$this->doc)) { 65 $this->PNitemCount--; 66 } else { 67 $eventdata = array( 68 'doc' => &$this->doc, 69 'pid' => $this->_getID() 70 ); 71 trigger_event('PLUGIN_PURPLENUMBERS_P_CLOSED', $eventdata); 72 } 73 } 74 75 function listitem_open($level, $node = false) { 76 $this->doc .= '<li class="level'.$level.'"'.$this->_getID(1,1).'>'; 77 } 78 79 function listcontent_close() { 80 $this->doc .= $this->_getLink().'</div>'.DOKU_LF; 81 } 82 83 function preformatted($text, $type='code') { 84 $this->doc .= '<pre class="'.$type.'"'.$this->_getID(1,1).'>'. 85 trim($this->_xmlEntities($text),"\n\r"). 86 $this->_getLink().'</pre>'.DOKU_LF; 87 } 88 89 function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { 90 $this->_counter['row_counter'] = 0; 91 $this->doc .= '<div class="table"><table class="inline"'.$this->_getID(1,1).'>'.DOKU_LF; 92 } 93 94 function table_close($pos = null){ 95 $this->doc .= '</table></div>'.$this->_getLink(1).DOKU_LF; 96 } 97 98 function php($text, $wrapper='code') { 99 global $conf; 100 101 if($conf['phpok']) { 102 ob_start(); 103 eval($text); 104 $this->doc .= ob_get_contents(); 105 ob_end_clean(); 106 } elseif($wrapper != 'code') { 107 $code = '<'.$wrapper.$this->_getID(1,1).' class="code php">'; 108 $code .= trim(p_xhtml_cached_geshi($text, 'php', false),"\n\r"); 109 $code .= $this->_getLink(); 110 $code .= '</'.$wrapper.'>'; 111 $this->doc .= $code; 112 } else { 113 $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 114 } 115 } 116 117 function html($text, $wrapper='code') { 118 global $conf; 119 120 if($conf['htmlok']){ 121 $this->doc .= $text; 122 } elseif($wrapper != 'code') { 123 $code = '<'.$wrapper.$this->_getID(1,1).' class="code html4strict">'; 124 $code .= trim(p_xhtml_cached_geshi($text, 'html4strict', false),"\n\r"); 125 $code .= $this->_getLink(); 126 $code .= '</'.$wrapper.'>'; 127 $this->doc .= $code; 128 } else { 129 $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 130 } 131 } 132 133 function _highlight($type, $text, $language=null, $filename=null, $options = null) { 134 global $conf; 135 global $ID; 136 global $lang; 137 138 if($filename){ 139 list($ext) = mimetype($filename,false); 140 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 141 $class = 'mediafile mf_'.$class; 142 143 $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 144 $this->doc .= '<dt><a href="'.exportlink($ID,'code',array('codeblock'=>$this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">'; 145 $this->doc .= hsc($filename); 146 $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 147 } 148 149 if ($text[0] == "\n") { 150 $text = substr($text, 1); 151 } 152 if (substr($text, -1) == "\n") { 153 $text = substr($text, 0, -1); 154 } 155 156 if ( is_null($language) ) { 157 $this->preformatted($text, $type); 158 } else { 159 $class = 'code'; 160 if($type != 'code') $class .= ' '.$type; 161 162 $this->doc .= "<pre class=\"$class $language\" ".$this->_getID(1,1).">". 163 p_xhtml_cached_geshi($text, $language, ''). 164 $this->_getLink().'</pre>'.DOKU_LF; 165 } 166 167 if($filename){ 168 $this->doc .= '</dd></dl>'.DOKU_LF; 169 } 170 171 $this->_codeblock++; 172 } 173 174 175 /** 176 * Builds Purple Number ID. 177 * 178 * $setCount: increases (1) or resets (2) $PNitemCount 179 * $wrap: wrap output in 'id=""' 180 * $noprefix: lets you get the current ID without its prefix 181 * $internalID: clean ID, if it needs to be used as an internal ID 182 */ 183 function _getID($setCount=0, $wrap=0, $noprefix=0, $internalID=0) { 184 if ($this->_displayPN()) { 185 186 if (!$internalID) { 187 $internalID = $this->getConf('internalID'); 188 } 189 190 if ($setCount == 1) { 191 //increase for each new paragraph, etc 192 $this->PNitemCount++; 193 } else if ($setCount == 2) { 194 //reset for each new section (headline) 195 $this->PNitemCount = 0; 196 } 197 198 // build prefix 199 if ($noprefix) { 200 $prefix = ''; 201 } else if ($this->getConf('uniqueness')) { 202 //site-wide 203 global $ID; 204 $prefix = $ID.'.'; 205 } else { 206 //page-wide 207 $prefix = 'ID'; 208 } 209 210 if ($this->getConf('numbering')) { 211 //hierarchical 212 $nodeID = preg_replace('/(\.0)*$/','',join('.',$this->node)); 213 $itemNo = str_replace(':0','',':'.$this->PNitemCount); 214 $out = $prefix.$nodeID.$itemNo; 215 } else { 216 //consecutive 217 $out = $prefix.$this->PNitemCount; 218 } 219 220 // if the ID should be re-usable as an anchor in an internal link 221 if ($internalID) { 222 // sectionID() will strip out ':' and '.' 223 $out = str_replace(array(':','.'), array('-','_'), $out); 224 $out = cleanID($out); 225 } 226 227 if ($wrap) return ' id="'.$out.'"'; 228 return $out; 229 } 230 return ''; 231 } 232 233 /** 234 * Creates a link to the current Purple Number ID. 235 * 236 * $outside: puts a p.pnlink around the link, useful if 237 * the link cannot be inside its corresponding element (e.g. tables) 238 */ 239 function _getLink($outside=0) { 240 if ($this->_displayPN()) { 241 $linkText = $this->getConf('linkText') ? $this->_getID() : '¶'; 242 $sep = $outside ? '' : ' '; 243 244 $pnlink = $sep.'<a href="#'.$this->_getID().'" class="pn" title="'.$this->getLang('sectionlink').'">'.$linkText.'</a>'; 245 $pnlink .= $this->_getAnnotationLink(); 246 247 if ($outside) { 248 return '<p class="pnlink">'.$pnlink.'</p>'; 249 } 250 return '<!--PN-->'.$pnlink; 251 // that <!--PN--> comment is added to be able to delete empty paragraphs in document_end() 252 } 253 return ''; 254 } 255 256 /** 257 * Creates a link to an annotation page per Purple Number. 258 */ 259 function _getAnnotationLink() { 260 $annotationPage = $this->getConf('annotationPage'); 261 if ($annotationPage) { 262 global $ID; 263 // resolve placeholders 264 $aID = str_replace( 265 array( 266 '@PN@', 267 '@PNID@', 268 '@ID@', 269 '@PAGE@' 270 ), 271 array( 272 $this->_getID(0,0,1,1), 273 $this->_getID(0,0,0,1), 274 $ID, 275 noNSorNS($ID) 276 ), 277 $annotationPage 278 ); 279 // in case linkText is only a pilcrow, only show the icon 280 $onlyIcon = $this->getConf('linkText') ? '' : 'onlyIcon'; 281 $sep = $onlyIcon ? ' ' : ' '; 282 283 return $sep.'<span class="pn '.$onlyIcon.'">'. 284 html_wikilink($aID,$this->getLang('comment')). 285 '</span>'; 286 } 287 return ''; 288 } 289 290 /** 291 * Checks if the adding of the Purple Number should be restricted 292 * (by configuration settings 'restrictionNS' and 'restrictionType'). 293 */ 294 function _displayPN() { 295 global $ID, $INFO, $ACT, $conf; 296 297 if (!page_exists($ID)) return false; 298 // only show PNs in the main content, not in included pages (like sidebars) 299 if ($ID != $INFO['id']) return false; 300 if ($ACT != 'show') return false; 301 if (!$this->getConf('includeStartpage') && noNS($ID)==$conf['start']) return false; 302 303 if ($this->getConf('restrictionNS')) { 304 $curRootNS = substr($ID, 0, strpos($ID,':')); 305 $restrictionNS = explode(',', $this->getConf('restrictionNS')); 306 $restrictionType = $this->getConf('restrictionType'); 307 308 foreach ($restrictionNS as $r) { 309 if (trim($r) == $curRootNS) { 310 if ($restrictionType) 311 return true; 312 return false; 313 } 314 } 315 if ($restrictionType) 316 return false; 317 return true; 318 } 319 return true; 320 } 321 322} 323 324