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