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