1<?php 2/** 3 * DokuWiki Plugin struct (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10use plugin\struct\meta\ConfigParser; 11use plugin\struct\meta\Search; 12use plugin\struct\meta\SearchConfig; 13use plugin\struct\meta\SearchException; 14use plugin\struct\meta\StructException; 15 16if (!defined('DOKU_INC')) die(); 17 18class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin { 19 /** 20 * @return string Syntax mode type 21 */ 22 public function getType() { 23 return 'substition'; 24 } 25 /** 26 * @return string Paragraph type 27 */ 28 public function getPType() { 29 return 'block'; 30 } 31 /** 32 * @return int Sort order - Low numbers go before high numbers 33 */ 34 public function getSort() { 35 return 155; 36 } 37 38 /** 39 * Connect lookup pattern to lexer. 40 * 41 * @param string $mode Parser mode 42 */ 43 public function connectTo($mode) { 44 $this->Lexer->addSpecialPattern('----+ *struct table *-+\n.*?\n----+', $mode, 'plugin_struct_table'); 45 } 46 47 48 /** 49 * Handle matches of the struct syntax 50 * 51 * @param string $match The match of the syntax 52 * @param int $state The state of the handler 53 * @param int $pos The position in the document 54 * @param Doku_Handler $handler The handler 55 * @return array Data for the renderer 56 */ 57 public function handle($match, $state, $pos, Doku_Handler $handler){ 58 59 $lines = explode("\n", $match); 60 array_shift($lines); 61 array_pop($lines); 62 63 try { 64 $parser = new ConfigParser($lines); 65 return $parser->getConfig(); 66 } catch (StructException $e) { 67 msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); 68 return null; 69 } 70 } 71 72 protected $sums = array(); 73 74 /** @var helper_plugin_struct_aggregation $dthlp */ 75 protected $dthlp = null; 76 77 /** 78 * Render xhtml output or metadata 79 * 80 * @param string $mode Renderer mode (supported modes: xhtml) 81 * @param Doku_Renderer $renderer The renderer 82 * @param array $data The data from the handler() function 83 * @return bool If rendering was successful. 84 */ 85 public function render($mode, Doku_Renderer $renderer, $data) { 86 if($mode != 'xhtml') return false; 87 if(!$data) return false; 88 $this->dthlp = $this->loadHelper('struct_aggregation'); 89 90 $clist = $data['cols']; 91 92 //reset counters 93 $this->sums = array(); 94 95 /** @var \helper_plugin_struct_config $confHlp */ 96 $confHlp = plugin_load('helper','struct_config'); 97 98 try { 99 global $INPUT; 100 101 $datasrt = $INPUT->str('datasrt'); 102 if ($datasrt) { 103 $data['sort'] = $confHlp->parseSort($datasrt); 104 } 105 $dataflt = $INPUT->arr('dataflt'); 106 if ($dataflt) { 107 foreach ($dataflt as $colcomp => $filter) { 108 $data['filter'][] = $confHlp->parseFilterLine('AND', $colcomp . $filter); 109 } 110 } 111 $search = new SearchConfig($data); 112 $rows = $search->execute(); 113 $cnt = count($rows); 114 115 if ($cnt === 0) { 116 //$this->nullList($data, $clist, $R); 117 //return true; 118 } 119 120 if ($data['limit'] && $cnt > $data['limit']) { 121 $rows = array_slice($rows, 0, $data['limit']); 122 } 123 124 $this->renderPreTable($mode, $renderer, $clist, $data); 125 $this->renderRows($mode, $renderer, $data, $rows); 126 $this->renderPostTable($mode, $renderer, $data, $cnt); 127 128 $renderer->doc .= ''; 129 } catch (StructException $e) { 130 msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); 131 } 132 133 return true; 134 } 135 136 /** 137 * create the pretext to the actual table rows 138 * 139 * @param $mode 140 * @param Doku_Renderer $renderer 141 * @param $clist 142 * @param $data 143 */ 144 protected function renderPreTable($mode, Doku_Renderer $renderer, $clist, $data) { 145 // Save current request params to not loose them 146 $cur_params = $this->dthlp->_get_current_param(); 147 148 $this->startScope($mode, $renderer); 149 $this->showActiveFilters($mode, $renderer, $cur_params); 150 $this->startTable($mode, $renderer); 151 $renderer->tablethead_open(); 152 $this->buildColumnHeaders($mode, $renderer, $clist, $data, $cur_params); 153 $this->addDynamicFilters($mode, $renderer, $data, $cur_params); 154 $renderer->tablethead_close(); 155 } 156 157 /** 158 * @param array $data 159 * @param int $rowcnt 160 * 161 * @return string 162 */ 163 private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) { 164 $this->summarize($mode, $renderer, $data, $this->sums); 165 $this->addLimitControls($mode, $renderer, $data, $rowcnt); 166 $this->finishTableAndScope($mode, $renderer); 167 } 168 169 /** 170 * if limit was set, add control 171 * 172 * @param $data 173 * @param $rowcnt 174 * 175 */ 176 protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) { 177 global $ID; 178 179 if($data['limit']) { 180 $renderer->tablerow_open(); 181 $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0))); 182 $offset = (int) $_REQUEST['dataofs']; 183 if($offset) { 184 $prev = $offset - $data['limit']; 185 if($prev < 0) { 186 $prev = 0; 187 } 188 189 // keep url params 190 $params = $this->dthlp->_a2ua('dataflt', $_REQUEST['dataflt']); 191 if(isset($_REQUEST['datasrt'])) { 192 $params['datasrt'] = $_REQUEST['datasrt']; 193 } 194 $params['dataofs'] = $prev; 195 196 if ($mode == 'xhtml') { 197 $renderer->doc .= '<a href="' . wl($ID, $params) . 198 '" title="' . $this->getLang('prev') . 199 '" class="prev">' . $this->getLang('prev') . '</a> '; 200 } else { 201 $renderer->internallink($ID,$this->getLang('prev')); 202 } 203 } 204 205 if($rowcnt > $data['limit']) { 206 $next = $offset + $data['limit']; 207 208 // keep url params 209 $params = $this->dthlp->_a2ua('dataflt', $_REQUEST['dataflt']); 210 if(isset($_REQUEST['datasrt'])) { 211 $params['datasrt'] = $_REQUEST['datasrt']; 212 } 213 $params['dataofs'] = $next; 214 215 if ($mode == 'xhtml') { 216 $renderer->doc .= '<a href="' . wl($ID, $params) . 217 '" title="' . $this->getLang('next') . 218 '" class="next">' . $this->getLang('next') . '</a>'; 219 } else { 220 $renderer->internallink($ID,$this->getLang('next')); 221 } 222 } 223 $renderer->tableheader_close(); 224 $renderer->tablerow_close(); 225 } 226 } 227 228 /** 229 * @param $mode 230 * @param Doku_Renderer $renderer 231 * @param $cur_params 232 */ 233 protected function showActiveFilters($mode, Doku_Renderer $renderer, $cur_params) { 234 global $ID, $INPUT; 235 236 if($mode == 'xhtml' && $INPUT->has('dataflt')) { 237 $filters = $INPUT->arr('dataflt'); 238 $confHelper = $this->loadHelper('struct_config'); 239 $fltrs = array(); 240 foreach($filters as $colcomp => $filter) { 241 $filter = $confHelper->parseFilterLine('', $colcomp.$filter); 242 if(strpos($filter[1], '~') !== false) { 243 if(strpos($filter[1], '!~') !== false) { 244 $comparator_value = '!~' . str_replace('%', '*', $filter[2]); 245 } else { 246 $comparator_value = '~' . str_replace('%', '', $filter[2]); 247 } 248 $fltrs[] = $filter[0] . $comparator_value; 249 } else { 250 $fltrs[] = $filter[0] . $filter[1] . $filter[2]; 251 } 252 } 253 254 $renderer->doc .= '<div class="filter">'; 255 $renderer->doc .= '<h4>' . sprintf($this->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>'; 256 $renderer->doc .= '<div class="resetfilter">'; 257 $renderer->internallink($ID, $this->getLang('tableresetfilter')); 258 $renderer->doc .= '</div>'; 259 $renderer->doc .= '</div>'; 260 } 261 } 262 263 /** 264 * @param $mode 265 * @param Doku_Renderer $renderer 266 * @param $data 267 * @param $cur_params 268 */ 269 protected function addDynamicFilters($mode, Doku_Renderer $renderer, $data, $cur_params) { 270 if ($mode != 'xhtml') return; 271 272 global $conf, $ID; 273 274 $html = ''; 275 if($data['dynfilters']) { 276 $html .= '<tr class="dataflt">'; 277 278 if($data['rownumbers']) { 279 $html .= '<th></th>'; 280 } 281 282 foreach($data['headers'] as $num => $head) { 283 $html .= '<th>'; 284 $form = new Doku_Form(array('method' => 'GET',)); 285 $form->_hidden = array(); 286 if(!$conf['userewrite']) { 287 $form->addHidden('id', $ID); 288 } 289 290 $key = 'dataflt[' . $data['cols'][$num] . '~' . ']'; 291 $val = isset($cur_params[$key]) ? $cur_params[$key] : ''; 292 293 // Add current request params 294 foreach($cur_params as $c_key => $c_val) { 295 if($c_val !== '' && $c_key !== $key) { 296 $form->addHidden($c_key, $c_val); 297 } 298 } 299 300 $form->addElement(form_makeField('text', $key, $val, '')); 301 $html .= $form->getForm(); 302 $html .= '</th>'; 303 } 304 $html .= '</tr>'; 305 $renderer->doc .= $html; 306 } 307 } 308 309 /** 310 * @param $mode 311 * @param Doku_Renderer $renderer 312 */ 313 private function startTable($mode, Doku_Renderer $renderer) { 314 $renderer->table_open(); 315 } 316 317 /** 318 * @param $mode 319 * @param Doku_Renderer $renderer 320 * @param $clist 321 * @param $data 322 * @param $cur_params 323 * 324 */ 325 protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $clist, $data, $cur_params) { 326 global $ID; 327 328 $renderer->tablerow_open(); 329 330 if($data['rownumbers']) { 331 $renderer->tableheader_open(); 332 $renderer->cdata('#'); 333 $renderer->tableheader_close(); 334 } 335 336 foreach($data['headers'] as $num => $head) { 337 $ckey = $clist[$num]; 338 339 $width = ''; 340 if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') { 341 $width = ' style="width: ' . $data['widths'][$num] . ';"'; 342 } 343 if ($mode == 'xhmtl') { 344 $renderer->doc .= '<th' . $width . '>'; 345 } else { 346 $renderer->tableheader_open(); 347 } 348 349 // add sort arrow 350 if ($mode == 'xhtml') { 351 if(isset($data['sort']) && $ckey == $data['sort'][0]) { 352 if($data['sort'][1] == 'ASC') { 353 $renderer->doc .= '<span>↓</span> '; 354 $ckey = '^' . $ckey; 355 } else { 356 $renderer->doc .= '<span>↑</span> '; 357 } 358 } 359 } 360 361 // Clickable header for dynamic sorting 362 if ($mode == 'xhtml') { 363 $renderer->doc .= '<a href="' . wl($ID, array('datasrt' => $ckey,) + $cur_params) . 364 '" title="' . $this->getLang('sort') . '">' . hsc($head) . '</a>'; 365 } else { 366 $renderer->internallink($ID . "?datasrt=$ckey", hsc($head)); 367 } 368 $renderer->tableheader_close(); 369 } 370 $renderer->tablerow_close(); 371 } 372 373 374 protected function startScope($mode, \Doku_Renderer $renderer) { 375 if ($mode == 'xhtml') { 376 $renderer->doc .= '<div class="table structaggegation">'; 377 } 378 } 379 380 /** 381 * if summarize was set, add sums 382 * 383 * @param $mode 384 * @param Doku_Renderer $renderer 385 * @param $data 386 * @param $sums 387 */ 388 private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) { 389 if($data['summarize']) { 390 $renderer->tablerow_open(); 391 $len = count($data['cols']); 392 393 if($data['rownumbers']) { 394 $renderer->tablecell_open(); 395 $renderer->tablecell_close(); 396 } 397 398 for($i = 0; $i < $len; $i++) { 399 $renderer->tablecell_open(1, $data['align'][$i]); 400 if(!empty($sums[$i])) { 401 $renderer->cdata('∑ ' . $sums[$i]); 402 } else { 403 if ($mode == 'xhtml') { 404 $renderer->doc .= ' '; 405 } 406 } 407 $renderer->tablecell_close(); 408 } 409 $renderer->tablerow_close(); 410 } 411 } 412 413 /** 414 * @param $mode 415 * @param Doku_Renderer $renderer 416 * 417 */ 418 private function finishTableAndScope($mode, Doku_Renderer $renderer) { 419 $renderer->table_close(); 420 if ($mode == 'xhmtl') { 421 $renderer->doc .= '</div>'; 422 } 423 } 424 425 /** 426 * @param $mode 427 * @param Doku_Renderer $renderer 428 * @param $data 429 * @param $rows 430 * 431 */ 432 private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) { 433 $renderer->tabletbody_open(); 434 foreach($rows as $rownum => $row) { 435 $renderer->tablerow_open(); 436 437 if($data['rownumbers']) { 438 $renderer->tablecell_open(); 439 $renderer->doc .= $rownum + 1; 440 $renderer->tablecell_close(); 441 } 442 443 /** @var plugin\struct\meta\Value $value */ 444 foreach($row as $colnum => $value) { 445 $renderer->tablecell_open(); 446 $value->render($renderer, $mode); 447 $renderer->tablecell_close(); 448 449 // summarize 450 if($data['summarize'] && is_numeric($value->getValue())) { 451 if(!isset($this->sums[$colnum])) { 452 $this->sums[$colnum] = 0; 453 } 454 $this->sums[$colnum] += $value->getValue(); 455 } 456 } 457 $renderer->tablerow_close(); 458 } 459 $renderer->tabletbody_close(); 460 } 461} 462 463// vim:ts=4:sw=4:et: 464