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