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