1<?php
2/**
3 * DokuWiki Plugin structcondstyle (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Lars Ohnemus <ohnemus.lars@gmail.com>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) {
11    die();
12}
13
14use dokuwiki\plugin\structcondstyle\meta\Operator;
15
16function make_numeric($value){
17    if(is_numeric($value))
18        return floatval($value);
19    if(is_string($value)){
20        if($value == "now")
21            return time();
22        else
23            return strtotime($value);
24
25    }
26    else
27        return FALSE;
28}
29
30function equal_func($lhs, $rhs){
31    return $lhs == $rhs;
32}
33
34function nequal_func($lhs, $rhs){
35    return !equal_func($lhs, $rhs);
36}
37
38function less_func($lhs, $rhs){
39    // Parse values to numeric
40    $lhs_num = make_numeric($lhs);
41    $rhs_num = make_numeric($rhs);
42    if($lhs_num === FALSE or $rhs_num === FALSE)
43        return false;
44    else
45        return $lhs_num < $rhs_num;
46}
47
48function leq_func($lhs, $rhs){
49    // Parse values to numeric
50    $lhs_num = make_numeric($lhs);
51    $rhs_num = make_numeric($rhs);
52    if($lhs_num === FALSE or $rhs_num === FALSE)
53        return false;
54    else
55        return $lhs_num <= $rhs_num;
56}
57
58function greater_func($lhs, $rhs){
59    // Parse values to numeric
60    $lhs_num = make_numeric($lhs);
61    $rhs_num = make_numeric($rhs);
62    if($lhs_num === FALSE or $rhs_num === FALSE)
63        return false;
64    else
65        return $lhs_num > $rhs_num;
66}
67
68function greq_func($lhs, $rhs){
69    // Parse values to numeric
70    $lhs_num = make_numeric($lhs);
71    $rhs_num = make_numeric($rhs);
72    if($lhs_num === FALSE or $rhs_num === FALSE)
73        return false;
74    else
75        return $lhs_num >= $rhs_num;
76}
77
78function contains_func($lhs, $rhs){
79    // Check if $lhs is array or string
80    if(is_array($lhs)){
81        return in_array($rhs,$lhs);
82    }else if(is_string($lhs)){
83        return strpos($lhs,$rhs) !== false;
84    }else return false;
85}
86
87
88class action_plugin_structcondstyle extends DokuWiki_Action_Plugin
89{
90    protected $search_configs = [];
91    protected $ops = [];
92
93
94    /**
95     * Registers a callback function for a given event
96     *
97     * @param Doku_Event_Handler $controller DokuWiki's event controller object
98     *
99     * @return void
100     */
101    public function register(Doku_Event_Handler $controller)
102    {
103        $this->ops = [  "="         => new Operator("=","equal_func"),
104                        "!="        => new Operator("!=","nequal_func"),
105                        "not"       => new Operator("not","nequal_func"),
106                        "<"         => new Operator("<","less_func"),
107                        "<="        => new Operator("<=","leq_func"),
108                        ">"         => new Operator(">","greater_func"),
109                        ">="        => new Operator(">=","greq_func"),
110                        "contains"  => new Operator("contains","contains_func")
111                    ];
112
113
114        $controller->register_hook('PLUGIN_STRUCT_CONFIGPARSER_UNKNOWNKEY', 'BEFORE', $this, 'handle_plugin_struct_configparser_unknownkey');
115        $controller->register_hook('PLUGIN_STRUCT_AGGREGATIONTABLE_RENDERRESULTROW', 'BEFORE', $this, 'handle_plugin_struct_aggregationtable_renderresultrow_before');
116        $controller->register_hook('PLUGIN_STRUCT_AGGREGATIONTABLE_RENDERRESULTROW', 'AFTER', $this, 'handle_plugin_struct_aggregationtable_renderresultrow_after');
117
118    }
119
120    /**
121     * [Custom event handler which performs action]
122     *
123     * Called for event:
124     *
125     * @param Doku_Event $event  event object by reference
126     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
127     *                           handler was registered]
128     *
129     * @return void
130     */
131    public function handle_plugin_struct_configparser_unknownkey(Doku_Event $event, $param)
132    {
133        // Retrieve the key and data and value passed for this agregation line
134        $data = $event->data;
135        $key = $data['key'];
136        $val = trim($data['val']);
137
138        // If the key is not associated with this plugin, return instantly
139        if ($key != 'condstyle') return;
140
141        // Else prevent errors and default handling to inject custom code into struct
142        $event->preventDefault();
143        $event->stopPropagation();
144
145        // Try to parse value into ternary statement and show error message if it does not work out
146        if(!preg_match('/\s*[a-zA-z]+\s*.+\s*[a-zA-z0-9]+\s*\?\s*".+"\s*:\s*.+"/',$val)){
147            msg("condstyle: $val is not correct", -1);
148            return;
149        }
150
151        // split value
152        $condition = preg_split("/\s*\?\s*/",$val,2)[0];
153        $styles = preg_split("/\s*\?\s*/",$val,2)[1];
154        $style_true = trim(preg_split('/"\s*:\s*"/',$styles)[0],'"');
155        $style_false = trim(preg_split('/"\s*:\s*"/',$styles)[1],'"');
156
157        // Parse operator
158        $operator = NULL;
159
160        foreach ($this->ops as $k => $op) {
161            $op_reg = $op->getReg();
162            if(preg_match("/\s*[a-zA-z]+\s*$op_reg\s*[a-zA-z0-9]+\s*/",$condition)){
163                $operator = $op_reg;
164            }
165        }
166
167        if(!$operator){
168            msg("condstyle: unknown operator ($val)", -1);
169        }
170
171        // parse column and argument
172        $column = trim(preg_split("/\s*$operator\s*/",$condition)[0]);
173        $argument = trim(preg_split("/\s*$operator\s*/",$condition)[1]);
174
175        // package parsed command into data
176        $config = array("operator"    => $operator,
177                        "column"      => $column,
178                        "argument"    => $argument,
179                        "style_true"  => $style_true,
180                        "style_false" => $style_false
181                        );
182
183        // Check if condstyle is already existing in data
184        if(!isset($data['config'][$key])){
185            $data['config'][$key] = [];
186        }
187
188        // Add command to data
189        $data['config'][$key][] = $config;
190    }
191
192     /**
193     * [Custom event handler which performs action]
194     *
195     * Called for event:
196     *
197     * @param Doku_Event $event  event object by reference
198     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
199     *                           handler was registered]
200     *
201     * @return void
202     */
203    public function handle_plugin_struct_aggregationtable_renderresultrow_before(Doku_Event $event, $param)
204    {
205        // Retrieve mode, renderer and data from this event
206        $mode = $event->data['mode'];
207        $renderer = $event->data['renderer'];
208        $data = $event->data['data'];
209
210        // Check if mode is xhtml otherwise return
211        if ($mode != 'xhtml') return;
212
213
214        // get all styles
215        $condstyles = $data['condstyle'];
216        if(!isset($condstyles)) return;
217
218        // loop over each style
219        foreach ($condstyles as $stylenum => $style) {
220
221            // Unpack condition and styles
222            $operator = NULL;
223            $column = NULL;
224            $argument = NULL;
225            extract($style);
226
227            // Check if valid data was send, otherwise move to next style
228            if(!$operator) continue;
229
230            // Query struct database to get full schema info (in case the column used for condition is not displayed)
231            /** @var SearchConfig $searchConfig */
232            $searchConfig = $event->data['searchConfig'];
233            $searchConfig_hash = spl_object_hash($searchConfig) . $stylenum;
234
235            if (!isset($this->search_configs[$searchConfig_hash])) {
236                // Add new Entry for this search configuration
237                $this->search_configs[$searchConfig_hash] = [];
238
239                // Retrieve Column
240                $cond_column = $searchConfig->findColumn($column);
241                //dbg(var_dump($cond_column));
242
243                // Add all columns to be sure that all information was retrieved and execute query
244                $searchConfig->addColumn('*');
245                $result = $searchConfig->execute();
246
247                // Check for each row if the condition matches and add store that information for later use
248                foreach ($result as $rownum => $row) {
249                    /** @var Value $value */
250                    foreach ($row as $colnum => $value) {
251                        if ($value->getColumn() === $cond_column) {
252                            $row_val = $value->getRawValue();
253                            // check condition
254                            $cond_applies = $this->ops[$operator]->evaluate($row_val,$argument);
255
256                            // store condition to inject style later
257                            $this->search_configs[$searchConfig_hash][$rownum] = $cond_applies;
258
259                            break;
260                        }
261                    }
262                }
263
264            }
265
266        }// END style loop
267
268        // save row start position
269        $event->data['rowstart']= mb_strlen($renderer->doc);
270
271    }
272
273     /**
274     * [Custom event handler which performs action]
275     *
276     * Called for event:
277     *
278     * @param Doku_Event $event  event object by reference
279     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
280     *                           handler was registered]
281     *
282     * @return void
283     */
284    public function handle_plugin_struct_aggregationtable_renderresultrow_after(Doku_Event $event, $param)
285    {
286        // Retrieve and check mode
287        $mode = $event->data['mode'];
288        if ($mode != 'xhtml') return;
289
290        // Retrieve renderer
291        $renderer = $event->data['renderer'];
292
293        // Retrieve searchconfig and data
294        /** @var SearchConfig $searchConfig */
295        $searchConfig = $event->data['searchConfig'];
296        $data = $event->data['data'];
297        $rownum  = $event->data['rownum'];
298        $rowstart = $event->data['rowstart'];
299
300        // get all styles
301        $condstyles = $data['condstyle'];
302        if(!isset($condstyles)) return;
303
304        // String to store all styles
305        $style_tag = "";
306
307        // loop over each style
308        foreach ($condstyles as $stylenum => $style) {
309            // Unpack condition and styles
310            $style_true = NULL;
311            $style_false = NULL;
312            extract($style);
313
314            // Check if proper info is available
315            if (!isset($style_true)) continue;
316            if (!isset($style_false)) continue;
317            if (!$rowstart) continue;
318
319
320            // Lookup the style condition
321            $searchConfig_hash = spl_object_hash($searchConfig) . $stylenum;
322            $cond_applies = $this->search_configs[$searchConfig_hash][$rownum];
323            if (!isset($cond_applies)) continue;
324
325            // set the style for this column based on condition
326            $style_tag .= $cond_applies ? $style_true : $style_false;
327
328        } // END style loop
329
330
331        // split doc to inject styling
332        $rest = mb_substr($renderer->doc, 0,  $rowstart);
333        $row = mb_substr($renderer->doc, $rowstart);
334        $row = ltrim($row);
335        //check if we processing row
336        if (mb_substr($row, 0, 3) != '<tr') return;
337
338        $tr_tag = mb_substr($row, 0, 3);
339        $tr_rest = mb_substr($row, 3);
340
341
342        // inject style into document
343        if(trim($style_tag) != "")
344            $renderer->doc = $rest . $tr_tag . ' style="'.$style_tag.'" ' . $tr_rest;
345
346
347    }
348
349}
350
351