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