xref: /plugin/struct/syntax/table.php (revision 1a07b69644a74cb7c512c56dff6a8a500d61eef7)
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    /** @var helper_plugin_struct_aggregation $dthlp  */
75    protected $dthlp = null;
76
77    /**
78     * Render xhtml output or metadata
79     *
80     * @param string         $mode      Renderer mode (supported modes: xhtml)
81     * @param Doku_Renderer  $renderer  The renderer
82     * @param array          $data      The data from the handler() function
83     * @return bool If rendering was successful.
84     */
85    public function render($mode, Doku_Renderer $renderer, $data) {
86        if($mode != 'xhtml') return false;
87        if(!$data) return false;
88        $this->dthlp = $this->loadHelper('struct_aggregation');
89
90        $clist = $data['cols'];
91
92        //reset counters
93        $this->sums = array();
94
95        try {
96            $search = new SearchConfig($data);
97            $data = $search->getConf();
98            $rows = $search->execute();
99            $cnt = count($rows);
100
101            if ($cnt === 0) {
102                //$this->nullList($data, $clist, $R);
103                //return true;
104            }
105
106            $this->renderPreTable($mode, $renderer, $clist, $data);
107            $this->renderRows($mode, $renderer, $data, $rows);
108            $this->renderPostTable($mode, $renderer, $data, $cnt);
109        } catch (StructException $e) {
110            msg($e->getMessage(), -1, $e->getLine(), $e->getFile());
111        }
112
113        return true;
114    }
115
116    /**
117     * create the pretext to the actual table rows
118     *
119     * @param               $mode
120     * @param Doku_Renderer $renderer
121     * @param               $clist
122     * @param               $data
123     */
124    protected function renderPreTable($mode, Doku_Renderer $renderer, $clist, $data) {
125        $this->startScope($mode, $renderer);
126        $this->showActiveFilters($mode, $renderer);
127        $this->startTable($mode, $renderer);
128        $renderer->tablethead_open();
129        $this->buildColumnHeaders($mode, $renderer, $clist, $data);
130        $this->addDynamicFilters($mode, $renderer, $data);
131        $renderer->tablethead_close();
132    }
133
134    /**
135     * @param array $data
136     * @param int $rowcnt
137     *
138     * @return string
139     */
140    private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) {
141        $this->summarize($mode, $renderer, $data, $this->sums);
142        $this->addLimitControls($mode, $renderer, $data, $rowcnt);
143        $this->finishTableAndScope($mode, $renderer);
144    }
145
146    /**
147     * if limit was set, add control
148     *
149     * @param               $mode
150     * @param Doku_Renderer $renderer
151     * @param               $data
152     * @param               $rowcnt
153     */
154    protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) {
155        global $ID;
156
157        if($data['limit']) {
158            $renderer->tablerow_open();
159            $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0)));
160            $offset = (int) $_REQUEST['dataofs'];
161
162            // keep url params
163            $params = array();
164            if (!empty($data['current_params']['dataflt'])) {$params['dataflt'] = $data['current_params']['dataflt'];}
165            if (!empty($data['current_params']['datasrt'])) {$params['datasrt'] = $data['current_params']['datasrt'];}
166
167            if($offset) {
168                $prev = $offset - $data['limit'];
169                if($prev < 0) {
170                    $prev = 0;
171                }
172                $params['dataofs'] = $prev;
173                $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('prev'));
174            }
175
176            if($rowcnt > $offset + $data['limit']) {
177                $next = $offset + $data['limit'];
178                $params['dataofs'] = $next;
179                $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('next'));
180            }
181            $renderer->tableheader_close();
182            $renderer->tablerow_close();
183        }
184    }
185
186    /**
187     * @param               $mode
188     * @param Doku_Renderer $renderer
189     */
190    protected function showActiveFilters($mode, Doku_Renderer $renderer) {
191        global $ID;
192
193        if($mode == 'xhtml' && !empty($data['current_params']['dataflt'])) {
194            $filters = $data['current_params']['dataflt'];
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               $mode
222     * @param Doku_Renderer $renderer
223     * @param               $data
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                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               $mode
274     * @param Doku_Renderer $renderer
275     */
276    private function startTable($mode, Doku_Renderer $renderer) {
277        $renderer->table_open();
278    }
279
280    /**
281     * @param               $mode
282     * @param Doku_Renderer $renderer
283     * @param               $clist
284     * @param               $data
285     *
286     */
287    protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $clist, $data) {
288        global $ID;
289
290        $renderer->tablerow_open();
291
292        if($data['rownumbers']) {
293            $renderer->tableheader_open();
294            $renderer->cdata('#');
295            $renderer->tableheader_close();
296        }
297
298        foreach($data['headers'] as $num => $head) {
299            $ckey = $clist[$num];
300
301            $width = '';
302            if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') {
303                $width = ' style="width: ' . $data['widths'][$num] . ';"';
304            }
305            if ($mode == 'xhmtl') {
306                $renderer->doc .= '<th' . $width . '>';
307            } else {
308                $renderer->tableheader_open();
309            }
310
311            // add sort arrow
312            if ($mode == 'xhtml') {
313                if(isset($data['sort']) && $ckey == $data['sort'][0]) {
314                    if($data['sort'][1] == 'ASC') {
315                        $renderer->doc .= '<span>&darr;</span> ';
316                        $ckey = '^' . $ckey;
317                    } else {
318                        $renderer->doc .= '<span>&uarr;</span> ';
319                    }
320                }
321            }
322            $renderer->internallink($ID . "?" . http_build_query(array('datasrt' => $ckey,) + $data['current_params']), hsc($head));
323            $renderer->tableheader_close();
324        }
325        $renderer->tablerow_close();
326    }
327
328
329    protected function startScope($mode, \Doku_Renderer $renderer) {
330        if ($mode == 'xhtml') {
331            $renderer->doc .= '<div class="table structaggegation">';
332        }
333    }
334
335    /**
336     * if summarize was set, add sums
337     *
338     * @param               $mode
339     * @param Doku_Renderer $renderer
340     * @param               $data
341     * @param               $sums
342     */
343    private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) {
344        if($data['summarize']) {
345            $renderer->tablerow_open();
346            $len = count($data['cols']);
347
348            if($data['rownumbers']) {
349                $renderer->tablecell_open();
350                $renderer->tablecell_close();
351            }
352
353            for($i = 0; $i < $len; $i++) {
354                $renderer->tablecell_open(1, $data['align'][$i]);
355                if(!empty($sums[$i])) {
356                    $renderer->cdata('∑ ' . $sums[$i]);
357                } else {
358                    if ($mode == 'xhtml') {
359                        $renderer->doc .= '&nbsp;';
360                    }
361                }
362                $renderer->tablecell_close();
363            }
364            $renderer->tablerow_close();
365        }
366    }
367
368    /**
369     * @param               $mode
370     * @param Doku_Renderer $renderer
371     *
372     */
373    private function finishTableAndScope($mode, Doku_Renderer $renderer) {
374        $renderer->table_close();
375        if ($mode == 'xhmtl') {
376            $renderer->doc .= '</div>';
377        }
378    }
379
380    /**
381     * @param               $mode
382     * @param Doku_Renderer $renderer
383     * @param               $data
384     * @param               $rows
385     *
386     */
387    private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) {
388        $renderer->tabletbody_open();
389        foreach($rows as $rownum => $row) {
390            $renderer->tablerow_open();
391
392            if($data['rownumbers']) {
393                $renderer->tablecell_open();
394                $renderer->doc .= $rownum + 1;
395                $renderer->tablecell_close();
396            }
397
398            /** @var plugin\struct\meta\Value $value */
399            foreach($row as $colnum => $value) {
400                $renderer->tablecell_open();
401                $value->render($renderer, $mode);
402                $renderer->tablecell_close();
403
404                // summarize
405                if($data['summarize'] && is_numeric($value->getValue())) {
406                    if(!isset($this->sums[$colnum])) {
407                        $this->sums[$colnum] = 0;
408                    }
409                    $this->sums[$colnum] += $value->getValue();
410                }
411            }
412            $renderer->tablerow_close();
413        }
414        $renderer->tabletbody_close();
415    }
416}
417
418// vim:ts=4:sw=4:et:
419