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