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