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            //var_dump($data);exit;
67            $data = array_pop($data);
68            $data = preg_split('/[\r\n]+/', $data, -1, PREG_SPLIT_NO_EMPTY);
69
70            //process wiki-data
71            $autoindex = 0;
72            foreach ($data as $entry) {
73                //normal const
74                $item = explode("=", trim($entry), 2);
75                if (count($item) === 2) {
76                    //special string-replace
77                    switch ($item[1]) {
78                        case "%USER%":
79                            $item[1] = $username;
80                            $invalidate = true;
81                            break; //pagename
82                        case "%ID%":
83                            $item[1] = noNS(cleanID(getID()));
84                            break; //pagename
85                        case "%NAMESPACE%":
86                            $item[1] = getNS(cleanID(getID()));
87                            break; //namespace
88                        case "%RANDOM%":
89                            $item[1] = strval(rand());
90                            $invalidate = true;
91                            break; //random number
92                        case "%YEAR%":
93                            $item[1] = date("Y");
94                            break; //current year
95                        case "%MONTH%":
96                            $item[1] = date("m");
97                            break; //current month
98                        case "%MONTHNAME%":
99                            $item[1] = date("F");
100                            break; //current month
101                        case "%WEEK%":
102                            $item[1] = date("W");
103                            $invalidate = true;
104                            break; //current week (iso)
105                        case "%DAY%":
106                            $item[1] = date("d");
107                            $invalidate = true;
108                            break; //current day
109                        case "%DAYNAME%":
110                            $item[1]  = date("l");
111                            $invalidate = true;
112                            break; //current day
113                        case "%HOUR%":
114                            $item[1]  = date("H");
115                            $invalidate = true;
116                            break; //current hour
117                        case "%MINUTE%":
118                            $item[1]  = date("i");
119                            $invalidate = true;
120                            break; //current minute
121                        case "%SECOND%":
122                            $item[1]  = date("s");
123                            $invalidate = true;
124                            break; //current second
125                        case "%UNIXEPOCH%":
126                            $item[1]  = date("U");
127                            $invalidate = true;
128                            break; //current unix timestamp
129                        case "%AUTOINDEX%":
130                            $item[1] = "%%INDEX#" . (++$autoindex) . "%%"; //special automatic indexer
131                            break;
132                        case "%REVISION%":
133                            $tmp_info = pageinfo();
134                            $item[1]  = $tmp_info['lastmod'];
135                            break; //current revision number (unix timestamp)
136                        case "%LASTMOD%":
137                            $tmp_info = pageinfo();
138                            $item[1]  = strftime("%Y%m%d_%H%M%S", $tmp_info['lastmod']);
139                            break; //last modification date
140                        default:
141                            $item[1] = trim($item[1]);
142                            break;
143                    }
144
145                    //replace in wiki
146                    $wikified = str_replace("%%" . trim($item[0]) . "%%", $item[1], $wikified);
147                    $wikified = str_replace("§§" . trim($item[0]) . "§§", $item[1], $wikified);
148
149                    //load evaluator
150                    @$math->assign_and_evaluate($item[0]."=".$item[1]);
151                } else {
152                    //evaluate expression
153                    $item = explode(":", $entry);
154                    if (count($item) === 2) {
155                        $wikified = str_replace("%%" . trim($item[0]) . "%%", @$math->assign_and_evaluate($item[1]), $wikified);
156                        $wikified = str_replace("§§" . trim($item[0]) . "§§", @$math->assign_and_evaluate($item[1]), $wikified);
157                    }
158                }
159            }
160
161            //autoindex?
162            while ($autoindex > 0) {
163                $this->autoindexer = 1;
164                //replace all
165                $wikified    = preg_replace_callback("|%%INDEX#" . $autoindex . "%%|", array(
166                    $this,
167                    "_replacecallback"
168                ), $wikified);
169                $autoindex--;
170            }
171
172            $event->data = $wikified;
173
174            $original = explode("\n", $original);
175            $wikified = explode("\n", $wikified);
176
177
178            $this->offsets[$ID] = array();
179            // fill offset array to deal with section editing issues
180            for ($l = 0; $l < count($wikified); $l++) {
181                // record offsets at the start of this line
182                $this->offsets[$ID][] = array(
183                    'pos' => $char_pos,
184                    'offset' => $text_offset
185                );
186                // calculate position / offset for next line
187                $char_pos += strlen($wikified[$l]) + 1;
188                $text_offset += strlen($wikified[$l]) - strlen($original[$l]);
189                //echo '(' . $char_pos . '/' . $text_offset . ')' . ' ';
190            }
191        }
192
193
194        //save invalidation info to metadata
195        p_set_metadata($ID, array(
196            'plugin_const' => array(
197                'invalidate' => $invalidate
198            )
199        ), false, true);
200    }
201
202    /**
203     * force cache invalidation for certain constants
204     */
205    function _cache_control(&$event, $param) {
206        global $conf;
207
208        $cache =& $event->data;
209
210        if ((isset($cache->page) === true) && ($cache->mode === "i")) {
211            //cache purge requested?
212            $const = p_get_metadata($cache->page, 'plugin_const');
213
214            //force initial purge
215            if (!isset($const['invalidate'])) {
216                $const['invalidate'] = true;
217            }
218
219            $cache->depends["purge"] = $const["invalidate"];
220        }
221    }
222
223    /**
224     * modifying the raw data has as side effect that the sectioning is based on the
225     * modified data, not the original. This means that after processing, we need to
226     * adjust the section start/end markers so that they point to start/end positions
227     * in the original data, not the modified data.
228     */
229    function _fixsecedit(&$event, $param) {
230        $calls =& $event->data->calls;
231        $count = count($calls);
232
233        // iterate through the instruction list and set the file offset values
234        // back to the values they would be if no const syntax has been added by this plugin
235        for ($i = 0; $i < $count; $i++) {
236            if (in_array($calls[$i][0],array(
237            		'section_open',
238            		'section_close',
239            		'header',
240            		'p_open',
241            		//'table_close',
242            		//'table_open',
243            	))) {
244                $calls[$i][2] = $this->_convert($calls[$i][2]);
245            }
246            if (in_array($calls[$i][0],array('header','table_open'))) {
247                $calls[$i][1][2] = $this->_convert($calls[$i][1][2]);
248            }
249            if(in_array($calls[$i][0],array('table_close'))) {
250            	$calls[$i][1][0] = $this->_convert($calls[$i][1][0]);
251            }
252            // be aware of headernofloat plugin
253            if ($calls[$i][0] === 'plugin' && $calls[$i][1][0] === 'headernofloat') {
254                $calls[$i][1][1]['pos'] = $this->_convert($calls[$i][1][1]['pos']);
255                $calls[$i][2] = $this->_convert($calls[$i][2]);
256            }
257            //if($calls[$i][0] == 'table_close')
258        }
259    }
260
261    /**
262     * Convert modified raw wiki offset value pos back to the unmodified value
263     */
264    function _convert($pos) {
265        global $ID;
266        if(!array_key_exists($ID,$this->offsets)) return $pos;
267        // find the offset that applies to this character position
268        $offset = 0;
269        foreach ($this->offsets[$ID] as $tuple) {
270            if ($pos >= $tuple['pos']) {
271                $offset = $tuple['offset'];
272            } else {
273                break;
274            }
275        }
276
277        // return corrected position
278        return $pos - $offset;
279    }
280}
281