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