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\SearchConfig; 13use plugin\struct\meta\StructException; 14 15if (!defined('DOKU_INC')) die(); 16 17class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin { 18 /** 19 * @return string Syntax mode type 20 */ 21 public function getType() { 22 return 'substition'; 23 } 24 /** 25 * @return string Paragraph type 26 */ 27 public function getPType() { 28 return 'block'; 29 } 30 /** 31 * @return int Sort order - Low numbers go before high numbers 32 */ 33 public function getSort() { 34 return 155; 35 } 36 37 /** 38 * Connect lookup pattern to lexer. 39 * 40 * @param string $mode Parser mode 41 */ 42 public function connectTo($mode) { 43 $this->Lexer->addSpecialPattern('----+ *struct table *-+\n.*?\n----+', $mode, 'plugin_struct_table'); 44 } 45 46 47 /** 48 * Handle matches of the struct syntax 49 * 50 * @param string $match The match of the syntax 51 * @param int $state The state of the handler 52 * @param int $pos The position in the document 53 * @param Doku_Handler $handler The handler 54 * @return array Data for the renderer 55 */ 56 public function handle($match, $state, $pos, Doku_Handler $handler){ 57 58 $lines = explode("\n", $match); 59 array_shift($lines); 60 array_pop($lines); 61 62 try { 63 $parser = new ConfigParser($lines); 64 return $parser->getConfig(); 65 } catch (StructException $e) { 66 msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); 67 return null; 68 } 69 } 70 71 protected $sums = array(); 72 73 /** 74 * Render xhtml output or metadata 75 * 76 * @param string $mode Renderer mode (supported modes: xhtml) 77 * @param Doku_Renderer $renderer The renderer 78 * @param array $data The data from the handler() function 79 * @return bool If rendering was successful. 80 */ 81 public function render($mode, Doku_Renderer $renderer, $data) { 82 if(!$data) return false; 83 84 if($mode == 'metadata') { 85 /** @var Doku_Renderer_metadata $renderer */ 86 $renderer->meta['plugin']['struct']['hasaggregation'] = true; 87 } 88 89 //reset counters 90 $this->sums = array(); 91 92 try { 93 $search = new SearchConfig($data); 94 $data = $search->getConf(); 95 $rows = $search->execute(); 96 $cnt = $search->getCount(); 97 $cols = $search->getColumns(); 98 99 if ($cnt === 0) { 100 $this->nullList($data, $mode, $renderer, $cols); 101 return true; 102 } 103 104 $this->renderPreTable($mode, $renderer, $data, $cols); 105 $this->renderRows($mode, $renderer, $data, $rows); 106 $this->renderPostTable($mode, $renderer, $data, $cnt); 107 } catch (StructException $e) { 108 msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); 109 } 110 111 return true; 112 } 113 114 /** 115 * create the pretext to the actual table rows 116 * 117 * @param string $mode 118 * @param Doku_Renderer $renderer 119 * @param array $data the configuration data 120 * @param Column[] $cols 121 */ 122 protected function renderPreTable($mode, Doku_Renderer $renderer, $data, $cols) { 123 $this->startScope($mode, $renderer, md5(serialize($data))); 124 $this->showActiveFilters($mode, $renderer); 125 $this->startTable($mode, $renderer); 126 $renderer->tablethead_open(); 127 $this->buildColumnHeaders($mode, $renderer, $data, $cols); 128 $this->addDynamicFilters($mode, $renderer, $data); 129 $renderer->tablethead_close(); 130 } 131 132 /** 133 * @param string $mode current render mode 134 * @param Doku_Renderer $renderer 135 * @param array $data 136 * @param int $rowcnt 137 * @return string 138 */ 139 private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) { 140 $this->summarize($mode, $renderer, $data, $this->sums); 141 $this->addLimitControls($mode, $renderer, $data, $rowcnt); 142 $this->finishTableAndScope($mode, $renderer); 143 } 144 145 /** 146 * if limit was set, add control 147 * 148 * @param string $mode the mode of the renderer 149 * @param Doku_Renderer $renderer the renderer 150 * @param array $data the configuration of the table/search 151 * @param $rowcnt 152 */ 153 protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) { 154 global $ID; 155 156 if($data['limit']) { 157 $renderer->tablerow_open(); 158 $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0))); 159 $offset = (int) $_REQUEST['dataofs']; 160 161 // keep url params 162 $params = array(); 163 if (!empty($data['current_params']['dataflt'])) {$params['dataflt'] = $data['current_params']['dataflt'];} 164 if (!empty($data['current_params']['datasrt'])) {$params['datasrt'] = $data['current_params']['datasrt'];} 165 166 if($offset) { 167 $prev = $offset - $data['limit']; 168 if($prev < 0) { 169 $prev = 0; 170 } 171 $params['dataofs'] = $prev; 172 $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('prev')); 173 } 174 175 if($rowcnt > $offset + $data['limit']) { 176 $next = $offset + $data['limit']; 177 $params['dataofs'] = $next; 178 $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('next')); 179 } 180 $renderer->tableheader_close(); 181 $renderer->tablerow_close(); 182 } 183 } 184 185 /** 186 * @param string $mode the mode of the renderer 187 * @param Doku_Renderer $renderer the renderer 188 */ 189 protected function showActiveFilters($mode, Doku_Renderer $renderer) { 190 global $ID; 191 192 if($mode == 'xhtml' && !empty($data['current_params']['dataflt'])) { 193 $filters = $data['current_params']['dataflt']; 194 /** @var helper_plugin_struct_config $confHelper */ 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 string $mode the mode of the renderer 222 * @param Doku_Renderer $renderer the renderer 223 * @param array $data the configuration of the table/search 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 if (!empty($cur_params['dataflt'])) 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 string $mode the mode of the renderer 274 * @param Doku_Renderer $renderer the renderer 275 */ 276 private function startTable($mode, Doku_Renderer $renderer) { 277 $renderer->table_open(); 278 } 279 280 /** 281 * @param string $mode the mode of the renderer 282 * @param Doku_Renderer $renderer the renderer 283 * @param array $data the configuration of the table/search 284 * @param Column[] $cols 285 */ 286 protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $data, $cols) { 287 global $ID; 288 289 $renderer->tablerow_open(); 290 291 if($data['rownumbers']) { 292 $renderer->tableheader_open(); 293 $renderer->cdata('#'); 294 $renderer->tableheader_close(); 295 } 296 297 foreach($data['headers'] as $num => $head) { 298 $ckey = $data['cols'][$num]; 299 if(blank($head)) { 300 if(isset($cols[$num]) && is_a($cols[$num], 'plugin\struct\meta\PageColumn')) { 301 $head = $this->getLang('pagelabel'); 302 }else if(isset($cols[$num]) && is_a($cols[$num], 'plugin\struct\meta\Column')) { 303 $head = $cols[$num]->getTranslatedLabel(); 304 } else { 305 $head = 'column '.$num; // this should never happen 306 } 307 } 308 309 $width = ''; 310 if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') { 311 $width = ' style="width: ' . $data['widths'][$num] . ';"'; 312 } 313 if ($mode == 'xhmtl') { 314 $renderer->doc .= '<th' . $width . '>'; 315 } else { 316 $renderer->tableheader_open(); 317 } 318 319 // output header 320 if ($mode == 'xhtml') { 321 $sort = ''; 322 if(isset($data['sort']) && $ckey == $data['sort'][0]) { 323 if($data['sort'][1] == 'ASC') { 324 $sort = 'sort-down'; 325 $ckey = '^' . $ckey; 326 } else { 327 $sort = 'sort-up'; 328 } 329 } 330 331 $params = $data['current_params']; 332 $params['datasrt'] = $ckey; 333 $link = wl($ID, $params); 334 $renderer->doc .= '<a href="'.$link.'" class="'.$sort.'" title="'.$this->getLang('sort').'">'.hsc($head).'</a>'; 335 } else { 336 $renderer->cdata($head); 337 } 338 339 $renderer->tableheader_close(); 340 } 341 $renderer->tablerow_close(); 342 } 343 344 /** 345 * @param string $mode the mode of the renderer 346 * @param Doku_Renderer $renderer the renderer 347 * @param string $hash hash to identify the table and group images in gallery 348 */ 349 protected function startScope($mode, \Doku_Renderer $renderer, $hash) { 350 if ($mode == 'xhtml') { 351 $renderer->doc .= "<div class=\"structaggregation\">"; 352 $renderer->info['struct_table_hash'] = $hash; 353 } 354 } 355 356 /** 357 * if summarize was set, add sums 358 * 359 * @param string $mode the mode of the renderer 360 * @param Doku_Renderer $renderer the renderer 361 * @param array $data the configuration of the table/search 362 * @param array $sums the summarized output of the numerical fields 363 */ 364 private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) { 365 if($data['summarize']) { 366 $renderer->tablerow_open(); 367 $len = count($data['cols']); 368 369 if($data['rownumbers']) { 370 $renderer->tablecell_open(); 371 $renderer->tablecell_close(); 372 } 373 374 for($i = 0; $i < $len; $i++) { 375 $renderer->tablecell_open(1, $data['align'][$i]); 376 if(!empty($sums[$i])) { 377 $renderer->cdata('∑ ' . $sums[$i]); 378 } else { 379 if ($mode == 'xhtml') { 380 $renderer->doc .= ' '; 381 } 382 } 383 $renderer->tablecell_close(); 384 } 385 $renderer->tablerow_close(); 386 } 387 } 388 389 /** 390 * @param string $mode the mode of the renderer 391 * @param Doku_Renderer $renderer the renderer 392 * 393 */ 394 private function finishTableAndScope($mode, Doku_Renderer $renderer) { 395 $renderer->table_close(); 396 if ($mode == 'xhtml') { 397 $renderer->doc .= '</div>'; 398 if(isset($renderer->info['struct_table_hash'])) { 399 unset($renderer->info['struct_table_hash']); 400 } 401 } 402 } 403 404 /** 405 * @param string $mode the mode of the renderer 406 * @param Doku_Renderer $renderer the renderer 407 * @param array $data the configuration of the table/search 408 * @param $rows 409 * 410 */ 411 private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) { 412 $renderer->tabletbody_open(); 413 foreach($rows as $rownum => $row) { 414 $renderer->tablerow_open(); 415 416 if($data['rownumbers']) { 417 $renderer->tablecell_open(); 418 $renderer->doc .= $rownum + 1; 419 $renderer->tablecell_close(); 420 } 421 422 /** @var plugin\struct\meta\Value $value */ 423 foreach($row as $colnum => $value) { 424 $renderer->tablecell_open(); 425 $value->render($renderer, $mode); 426 $renderer->tablecell_close(); 427 428 // summarize 429 if($data['summarize'] && is_numeric($value->getValue())) { 430 if(!isset($this->sums[$colnum])) { 431 $this->sums[$colnum] = 0; 432 } 433 $this->sums[$colnum] += $value->getValue(); 434 } 435 } 436 $renderer->tablerow_close(); 437 } 438 $renderer->tabletbody_close(); 439 } 440 441 /** 442 * @param array $data the configuration of the table/search 443 * @param string $mode the mode of the renderer 444 * @param Doku_Renderer $renderer the renderer 445 * @param Column[] $cols 446 */ 447 private function nullList($data, $mode, Doku_Renderer $renderer, $cols) { 448 $this->renderPreTable($mode, $renderer, $data, $cols); 449 $renderer->tablerow_open(); 450 $renderer->tablecell_open(count($data['cols']) + $data['rownumbers'], 'center'); 451 $renderer->cdata($this->getLang('none')); 452 $renderer->tablecell_close(); 453 $renderer->tablerow_close(); 454 $renderer->table_close(); 455 } 456} 457 458// vim:ts=4:sw=4:et: 459