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 $dataofs = $INPUT->has('dataofs') ? $INPUT->int('dataofs') : 0; 121 if ($data['limit'] && $cnt > $data['limit']) { 122 $rows = array_slice($rows, $dataofs, $data['limit']); 123 } 124 125 $this->renderPreTable($mode, $renderer, $clist, $data); 126 $this->renderRows($mode, $renderer, $data, $rows); 127 $this->renderPostTable($mode, $renderer, $data, $cnt); 128 129 $renderer->doc .= ''; 130 } catch (StructException $e) { 131 msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); 132 } 133 134 return true; 135 } 136 137 /** 138 * create the pretext to the actual table rows 139 * 140 * @param $mode 141 * @param Doku_Renderer $renderer 142 * @param $clist 143 * @param $data 144 */ 145 protected function renderPreTable($mode, Doku_Renderer $renderer, $clist, $data) { 146 // Save current request params to not loose them 147 $cur_params = $this->dthlp->_get_current_param(); 148 149 $this->startScope($mode, $renderer); 150 $this->showActiveFilters($mode, $renderer); 151 $this->startTable($mode, $renderer); 152 $renderer->tablethead_open(); 153 $this->buildColumnHeaders($mode, $renderer, $clist, $data, $cur_params); 154 $this->addDynamicFilters($mode, $renderer, $data, $cur_params); 155 $renderer->tablethead_close(); 156 } 157 158 /** 159 * @param array $data 160 * @param int $rowcnt 161 * 162 * @return string 163 */ 164 private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) { 165 $this->summarize($mode, $renderer, $data, $this->sums); 166 $this->addLimitControls($mode, $renderer, $data, $rowcnt); 167 $this->finishTableAndScope($mode, $renderer); 168 } 169 170 /** 171 * if limit was set, add control 172 * 173 * @param $mode 174 * @param Doku_Renderer $renderer 175 * @param $data 176 * @param $rowcnt 177 */ 178 protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) { 179 global $ID, $INPUT; 180 181 if($data['limit']) { 182 $renderer->tablerow_open(); 183 $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0))); 184 $offset = (int) $_REQUEST['dataofs']; 185 186 // keep url params 187 $params = array(); 188 $params['dataflt'] = $INPUT->arr('dataflt'); 189 if ($INPUT->has('datasrt')) {$params['datasrt'] = $INPUT->str('datasrt');} 190 191 if($offset) { 192 $prev = $offset - $data['limit']; 193 if($prev < 0) { 194 $prev = 0; 195 } 196 $params['dataofs'] = $prev; 197 $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('prev')); 198 } 199 200 if($rowcnt > $offset + $data['limit']) { 201 $next = $offset + $data['limit']; 202 $params['dataofs'] = $next; 203 $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('next')); 204 } 205 $renderer->tableheader_close(); 206 $renderer->tablerow_close(); 207 } 208 } 209 210 /** 211 * @param $mode 212 * @param Doku_Renderer $renderer 213 */ 214 protected function showActiveFilters($mode, Doku_Renderer $renderer) { 215 global $ID, $INPUT; 216 217 if($mode == 'xhtml' && $INPUT->has('dataflt')) { 218 $filters = $INPUT->arr('dataflt'); 219 $confHelper = $this->loadHelper('struct_config'); 220 $fltrs = array(); 221 foreach($filters as $colcomp => $filter) { 222 $filter = $confHelper->parseFilterLine('', $colcomp.$filter); 223 if(strpos($filter[1], '~') !== false) { 224 if(strpos($filter[1], '!~') !== false) { 225 $comparator_value = '!~' . str_replace('%', '*', $filter[2]); 226 } else { 227 $comparator_value = '~' . str_replace('%', '', $filter[2]); 228 } 229 $fltrs[] = $filter[0] . $comparator_value; 230 } else { 231 $fltrs[] = $filter[0] . $filter[1] . $filter[2]; 232 } 233 } 234 235 $renderer->doc .= '<div class="filter">'; 236 $renderer->doc .= '<h4>' . sprintf($this->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>'; 237 $renderer->doc .= '<div class="resetfilter">'; 238 $renderer->internallink($ID, $this->getLang('tableresetfilter')); 239 $renderer->doc .= '</div>'; 240 $renderer->doc .= '</div>'; 241 } 242 } 243 244 /** 245 * @param $mode 246 * @param Doku_Renderer $renderer 247 * @param $data 248 * @param $cur_params 249 */ 250 protected function addDynamicFilters($mode, Doku_Renderer $renderer, $data, $cur_params) { 251 if ($mode != 'xhtml') return; 252 253 global $conf, $ID; 254 255 $html = ''; 256 if($data['dynfilters']) { 257 $html .= '<tr class="dataflt">'; 258 259 if($data['rownumbers']) { 260 $html .= '<th></th>'; 261 } 262 263 foreach($data['headers'] as $num => $head) { 264 $html .= '<th>'; 265 $form = new Doku_Form(array('method' => 'GET',)); 266 $form->_hidden = array(); 267 if(!$conf['userewrite']) { 268 $form->addHidden('id', $ID); 269 } 270 271 $key = $data['cols'][$num] . '*~'; 272 $val = isset($cur_params['dataflt'][$key]) ? $cur_params['dataflt'][$key] : ''; 273 274 // Add current request params 275 if (!empty($cur_params['datasrt'])) { 276 $form->addHidden('datasrt', $cur_params['datasrt']); 277 } 278 if (!empty($cur_params['dataofs'])) { 279 $form->addHidden('dataofs', $cur_params['dataofs']); 280 } 281 foreach($cur_params['dataflt'] as $c_key => $c_val) { 282 if($c_val !== '' && $c_key !== $key) { 283 $form->addHidden('dataflt[' . $c_key . ']', $c_val); 284 } 285 } 286 287 $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $val, '')); 288 $html .= $form->getForm(); 289 $html .= '</th>'; 290 } 291 $html .= '</tr>'; 292 $renderer->doc .= $html; 293 } 294 } 295 296 /** 297 * @param $mode 298 * @param Doku_Renderer $renderer 299 */ 300 private function startTable($mode, Doku_Renderer $renderer) { 301 $renderer->table_open(); 302 } 303 304 /** 305 * @param $mode 306 * @param Doku_Renderer $renderer 307 * @param $clist 308 * @param $data 309 * @param $cur_params 310 * 311 */ 312 protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $clist, $data, $cur_params) { 313 global $ID; 314 315 $renderer->tablerow_open(); 316 317 if($data['rownumbers']) { 318 $renderer->tableheader_open(); 319 $renderer->cdata('#'); 320 $renderer->tableheader_close(); 321 } 322 323 foreach($data['headers'] as $num => $head) { 324 $ckey = $clist[$num]; 325 326 $width = ''; 327 if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') { 328 $width = ' style="width: ' . $data['widths'][$num] . ';"'; 329 } 330 if ($mode == 'xhmtl') { 331 $renderer->doc .= '<th' . $width . '>'; 332 } else { 333 $renderer->tableheader_open(); 334 } 335 336 // add sort arrow 337 if ($mode == 'xhtml') { 338 if(isset($data['sort']) && $ckey == $data['sort'][0]) { 339 if($data['sort'][1] == 'ASC') { 340 $renderer->doc .= '<span>↓</span> '; 341 $ckey = '^' . $ckey; 342 } else { 343 $renderer->doc .= '<span>↑</span> '; 344 } 345 } 346 } 347 $renderer->internallink($ID . "?" . http_build_query(array('datasrt' => $ckey,) + $cur_params), hsc($head)); 348 $renderer->tableheader_close(); 349 } 350 $renderer->tablerow_close(); 351 } 352 353 354 protected function startScope($mode, \Doku_Renderer $renderer) { 355 if ($mode == 'xhtml') { 356 $renderer->doc .= '<div class="table structaggegation">'; 357 } 358 } 359 360 /** 361 * if summarize was set, add sums 362 * 363 * @param $mode 364 * @param Doku_Renderer $renderer 365 * @param $data 366 * @param $sums 367 */ 368 private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) { 369 if($data['summarize']) { 370 $renderer->tablerow_open(); 371 $len = count($data['cols']); 372 373 if($data['rownumbers']) { 374 $renderer->tablecell_open(); 375 $renderer->tablecell_close(); 376 } 377 378 for($i = 0; $i < $len; $i++) { 379 $renderer->tablecell_open(1, $data['align'][$i]); 380 if(!empty($sums[$i])) { 381 $renderer->cdata('∑ ' . $sums[$i]); 382 } else { 383 if ($mode == 'xhtml') { 384 $renderer->doc .= ' '; 385 } 386 } 387 $renderer->tablecell_close(); 388 } 389 $renderer->tablerow_close(); 390 } 391 } 392 393 /** 394 * @param $mode 395 * @param Doku_Renderer $renderer 396 * 397 */ 398 private function finishTableAndScope($mode, Doku_Renderer $renderer) { 399 $renderer->table_close(); 400 if ($mode == 'xhmtl') { 401 $renderer->doc .= '</div>'; 402 } 403 } 404 405 /** 406 * @param $mode 407 * @param Doku_Renderer $renderer 408 * @param $data 409 * @param $rows 410 * 411 */ 412 private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) { 413 $renderer->tabletbody_open(); 414 foreach($rows as $rownum => $row) { 415 $renderer->tablerow_open(); 416 417 if($data['rownumbers']) { 418 $renderer->tablecell_open(); 419 $renderer->doc .= $rownum + 1; 420 $renderer->tablecell_close(); 421 } 422 423 /** @var plugin\struct\meta\Value $value */ 424 foreach($row as $colnum => $value) { 425 $renderer->tablecell_open(); 426 $value->render($renderer, $mode); 427 $renderer->tablecell_close(); 428 429 // summarize 430 if($data['summarize'] && is_numeric($value->getValue())) { 431 if(!isset($this->sums[$colnum])) { 432 $this->sums[$colnum] = 0; 433 } 434 $this->sums[$colnum] += $value->getValue(); 435 } 436 } 437 $renderer->tablerow_close(); 438 } 439 $renderer->tabletbody_close(); 440 } 441} 442 443// vim:ts=4:sw=4:et: 444