1<?php 2/** 3 * DokuWiki Plugin const (Action Component) 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author lisps 7 */ 8 9if (!defined('DOKU_INC')) 10 define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 11if (!defined('DOKU_PLUGIN')) 12 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 13 14require_once(DOKU_PLUGIN . 'action.php'); 15 16/** 17 * All DokuWiki plugins to extend the parser/rendering mechanism 18 * need to inherit from this class 19 */ 20class action_plugin_const extends DokuWiki_Action_Plugin { 21 /** 22 * return variable 23 */ 24 private $autoindexer = 0; 25 26 27 /** 28 * offsets-at-character-position. recorded as tuples page:{charpos,offset}, where charpos is the 29 * position in the MODIFIED data, not the position in the original data, and offset is the 30 * CUMULATIVE offset at that position, not the offset relative to the previous location. 31 */ 32 private $offsets = array(); 33 34 //hook before rendering starts 35 function register( Doku_Event_Handler $controller) { 36 $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, '_doreplace'); 37 $controller->register_hook('PARSER_HANDLER_DONE', 'BEFORE', $this, '_fixsecedit'); 38 $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, '_cache_control'); 39 } 40 41 42 function _replacecallback($hits) { 43 return ($this->autoindexer++); 44 } 45 46 //trigger 47 function _doreplace(&$event, $param) { 48 global $ID; 49 50 require_once(dirname(__FILE__) . "/class.evalmath.php"); 51 $math = new evalmath(); 52 53 $this->autoindexer = 0; 54 $invalidate = false; 55 56 $original = $event->data; 57 $wikified = $original; 58 59 //catch anonymous access 60 $username=isset($_SERVER['REMOTE_USER'])?$_SERVER['REMOTE_USER']:"anonymous"; 61 62 //get const definitions 63 $data = array(); 64 if (preg_match('/<const[^>]*>([^<]*)<\/const>/', $event->data, $data) > 0) { 65 //split entries 66 $data = array_pop($data); 67 $data = preg_split('/[\r\n]+/', $data, -1, PREG_SPLIT_NO_EMPTY); 68 69 //process wiki-data 70 $autoindex = 0; 71 foreach ($data as $entry) { 72 //normal const 73 $item = explode("=", trim($entry)); 74 if (count($item) === 2) { 75 //special string-replace 76 switch ($item[1]) { 77 case "%USER%": 78 $item[1] = $username; 79 $invalidate = true; 80 break; //pagename 81 case "%ID%": 82 $item[1] = noNS(cleanID(getID())); 83 break; //pagename 84 case "%NAMESPACE%": 85 $item[1] = getNS(cleanID(getID())); 86 break; //namespace 87 case "%RANDOM%": 88 $item[1] = strval(rand()); 89 $invalidate = true; 90 break; //random number 91 case "%YEAR%": 92 $item[1] = date("Y"); 93 break; //current year 94 case "%MONTH%": 95 $item[1] = date("m"); 96 break; //current month 97 case "%MONTHNAME%": 98 $item[1] = date("F"); 99 break; //current month 100 case "%WEEK%": 101 $item[1] = date("W"); 102 $invalidate = true; 103 break; //current week (iso) 104 case "%DAY%": 105 $item[1] = date("d"); 106 $invalidate = true; 107 break; //current day 108 case "%DAYNAME%": 109 $item[1] = date("l"); 110 $invalidate = true; 111 break; //current day 112 case "%HOUR%": 113 $item[1] = date("H"); 114 $invalidate = true; 115 break; //current hour 116 case "%MINUTE%": 117 $item[1] = date("i"); 118 $invalidate = true; 119 break; //current minute 120 case "%SECOND%": 121 $item[1] = date("s"); 122 $invalidate = true; 123 break; //current second 124 case "%UNIXEPOCH%": 125 $item[1] = date("U"); 126 $invalidate = true; 127 break; //current unix timestamp 128 case "%AUTOINDEX%": 129 $item[1] = "%%INDEX#" . (++$autoindex) . "%%"; //special automatic indexer 130 break; 131 case "%REVISION%": 132 $tmp_info = pageinfo(); 133 $item[1] = $tmp_info['lastmod']; 134 break; //current revision number (unix timestamp) 135 case "%LASTMOD%": 136 $tmp_info = pageinfo(); 137 $item[1] = strftime("%Y%m%d_%H%M%S", $tmp_info['lastmod']); 138 break; //last modification date 139 default: 140 $item[1] = trim($item[1]); 141 break; 142 } 143 144 //replace in wiki 145 $wikified = str_replace("%%" . trim($item[0]) . "%%", $item[1], $wikified); 146 147 //load evaluator 148 @$math->evaluate($item[0]."=".$item[1]); 149 } else { 150 //evaluate expression 151 $item = explode(":", $entry); 152 if (count($item) === 2) { 153 $wikified = str_replace("%%" . trim($item[0]) . "%%", @$math->evaluate($item[1]), $wikified); 154 } 155 } 156 } 157 158 //autoindex? 159 while ($autoindex > 0) { 160 $this->autoindexer = 1; 161 //replace all 162 $wikified = preg_replace_callback("|%%INDEX#" . $autoindex . "%%|", array( 163 $this, 164 "_replacecallback" 165 ), $wikified); 166 $autoindex--; 167 } 168 169 $event->data = $wikified; 170 171 $original = explode("\n", $original); 172 $wikified = explode("\n", $wikified); 173 174 175 $this->offsets[$ID] = array(); 176 // fill offset array to deal with section editing issues 177 for ($l = 0; $l < count($wikified); $l++) { 178 // record offsets at the start of this line 179 $this->offsets[$ID][] = array( 180 'pos' => $char_pos, 181 'offset' => $text_offset 182 ); 183 // calculate position / offset for next line 184 $char_pos += strlen($wikified[$l]) + 1; 185 $text_offset += strlen($wikified[$l]) - strlen($original[$l]); 186 //echo '(' . $char_pos . '/' . $text_offset . ')' . ' '; 187 } 188 } 189 190 191 //save invalidation info to metadata 192 p_set_metadata($ID, array( 193 'plugin_const' => array( 194 'invalidate' => $invalidate 195 ) 196 ), false, true); 197 } 198 199 /** 200 * force cache invalidation for certain constants 201 */ 202 function _cache_control(&$event, $param) { 203 global $conf; 204 205 $cache =& $event->data; 206 207 if ((isset($cache->page) === true) && ($cache->mode === "i")) { 208 //cache purge requested? 209 $const = p_get_metadata($cache->page, 'plugin_const'); 210 211 //force initial purge 212 if (!isset($const['invalidate'])) { 213 $const['invalidate'] = true; 214 } 215 216 $cache->depends["purge"] = $const["invalidate"]; 217 } 218 } 219 220 /** 221 * modifying the raw data has as side effect that the sectioning is based on the 222 * modified data, not the original. This means that after processing, we need to 223 * adjust the section start/end markers so that they point to start/end positions 224 * in the original data, not the modified data. 225 */ 226 function _fixsecedit(&$event, $param) { 227 $calls =& $event->data->calls; 228 $count = count($calls); 229 230 // iterate through the instruction list and set the file offset values 231 // back to the values they would be if no const syntax has been added by this plugin 232 for ($i = 0; $i < $count; $i++) { 233 if (in_array($calls[$i][0],array( 234 'section_open', 235 'section_close', 236 'header', 237 'p_open', 238 //'table_close', 239 //'table_open', 240 ))) { 241 $calls[$i][2] = $this->_convert($calls[$i][2]); 242 } 243 if (in_array($calls[$i][0],array('header','table_open'))) { 244 $calls[$i][1][2] = $this->_convert($calls[$i][1][2]); 245 } 246 if(in_array($calls[$i][0],array('table_close'))) { 247 $calls[$i][1][0] = $this->_convert($calls[$i][1][0]); 248 } 249 // be aware of headernofloat plugin 250 if ($calls[$i][0] === 'plugin' && $calls[$i][1][0] === 'headernofloat') { 251 $calls[$i][1][1]['pos'] = $this->_convert($calls[$i][1][1]['pos']); 252 $calls[$i][2] = $this->_convert($calls[$i][2]); 253 } 254 //if($calls[$i][0] == 'table_close') 255 } 256 } 257 258 /** 259 * Convert modified raw wiki offset value pos back to the unmodified value 260 */ 261 function _convert($pos) { 262 global $ID; 263 if(!array_key_exists($ID,$this->offsets)) return $pos; 264 // find the offset that applies to this character position 265 $offset = 0; 266 foreach ($this->offsets[$ID] as $tuple) { 267 if ($pos >= $tuple['pos']) { 268 $offset = $tuple['offset']; 269 } else { 270 break; 271 } 272 } 273 274 // return corrected position 275 return $pos - $offset; 276 } 277} 278