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