xref: /plugin/struct/syntax/table.php (revision 4f4432112bd2f4cd20c6923e4db187cb7969e3d5)
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>&darr;</span> ';
308                        $ckey = '^' . $ckey;
309                    } else {
310                        $renderer->doc .= '<span>&uarr;</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 .= '&nbsp;';
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