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