xref: /plugin/struct/syntax/table.php (revision 68c0d8be50080c254174b0efaf6e9307e8ef1517)
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\Column;
11use plugin\struct\meta\ConfigParser;
12use plugin\struct\meta\Search;
13use plugin\struct\meta\SearchConfig;
14use plugin\struct\meta\SearchException;
15use plugin\struct\meta\StructException;
16
17if (!defined('DOKU_INC')) die();
18
19class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin {
20    /**
21     * @return string Syntax mode type
22     */
23    public function getType() {
24        return 'substition';
25    }
26    /**
27     * @return string Paragraph type
28     */
29    public function getPType() {
30        return 'block';
31    }
32    /**
33     * @return int Sort order - Low numbers go before high numbers
34     */
35    public function getSort() {
36        return 155;
37    }
38
39    /**
40     * Connect lookup pattern to lexer.
41     *
42     * @param string $mode Parser mode
43     */
44    public function connectTo($mode) {
45        $this->Lexer->addSpecialPattern('----+ *struct table *-+\n.*?\n----+', $mode, 'plugin_struct_table');
46    }
47
48
49    /**
50     * Handle matches of the struct syntax
51     *
52     * @param string $match The match of the syntax
53     * @param int    $state The state of the handler
54     * @param int    $pos The position in the document
55     * @param Doku_Handler    $handler The handler
56     * @return array Data for the renderer
57     */
58    public function handle($match, $state, $pos, Doku_Handler $handler){
59
60        $lines = explode("\n", $match);
61        array_shift($lines);
62        array_pop($lines);
63
64        try {
65            $parser = new ConfigParser($lines);
66            return  $parser->getConfig();
67        } catch (StructException $e) {
68            msg($e->getMessage(), -1, $e->getLine(), $e->getFile());
69            return null;
70        }
71    }
72
73    protected $sums = array();
74
75    /**
76     * Render xhtml output or metadata
77     *
78     * @param string         $mode      Renderer mode (supported modes: xhtml)
79     * @param Doku_Renderer  $renderer  The renderer
80     * @param array          $data      The data from the handler() function
81     * @return bool If rendering was successful.
82     */
83    public function render($mode, Doku_Renderer $renderer, $data) {
84        if($mode != 'xhtml') return false;
85        if(!$data) return false;
86
87        //reset counters
88        $this->sums = array();
89
90        try {
91            $search = new SearchConfig($data);
92            $data = $search->getConf();
93            $rows = $search->execute();
94            $cnt = $search->getCount();
95            $cols = $search->getColumns();
96
97            if ($cnt === 0) {
98                $this->nullList($data, $mode, $renderer, $cols);
99                return true;
100            }
101
102            $this->renderPreTable($mode, $renderer, $data, $cols);
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 string $mode
116     * @param Doku_Renderer $renderer
117     * @param array $data the configuration data
118     * @param Column[] $cols
119     */
120    protected function renderPreTable($mode, Doku_Renderer $renderer, $data, $cols) {
121        $this->startScope($mode, $renderer, md5(serialize($data)));
122        $this->showActiveFilters($mode, $renderer);
123        $this->startTable($mode, $renderer);
124        $renderer->tablethead_open();
125        $this->buildColumnHeaders($mode, $renderer, $data, $cols);
126        $this->addDynamicFilters($mode, $renderer, $data);
127        $renderer->tablethead_close();
128    }
129
130    /**
131     * @param string $mode current render mode
132     * @param Doku_Renderer $renderer
133     * @param array $data
134     * @param int $rowcnt
135     * @return string
136     */
137    private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) {
138        $this->summarize($mode, $renderer, $data, $this->sums);
139        $this->addLimitControls($mode, $renderer, $data, $rowcnt);
140        $this->finishTableAndScope($mode, $renderer);
141    }
142
143    /**
144     * if limit was set, add control
145     *
146     * @param string        $mode     the mode of the renderer
147     * @param Doku_Renderer $renderer the renderer
148     * @param array         $data     the configuration of the table/search
149     * @param               $rowcnt
150     */
151    protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) {
152        global $ID;
153
154        if($data['limit']) {
155            $renderer->tablerow_open();
156            $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0)));
157            $offset = (int) $_REQUEST['dataofs'];
158
159            // keep url params
160            $params = array();
161            if (!empty($data['current_params']['dataflt'])) {$params['dataflt'] = $data['current_params']['dataflt'];}
162            if (!empty($data['current_params']['datasrt'])) {$params['datasrt'] = $data['current_params']['datasrt'];}
163
164            if($offset) {
165                $prev = $offset - $data['limit'];
166                if($prev < 0) {
167                    $prev = 0;
168                }
169                $params['dataofs'] = $prev;
170                $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('prev'));
171            }
172
173            if($rowcnt > $offset + $data['limit']) {
174                $next = $offset + $data['limit'];
175                $params['dataofs'] = $next;
176                $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('next'));
177            }
178            $renderer->tableheader_close();
179            $renderer->tablerow_close();
180        }
181    }
182
183    /**
184     * @param string        $mode     the mode of the renderer
185     * @param Doku_Renderer $renderer the renderer
186     */
187    protected function showActiveFilters($mode, Doku_Renderer $renderer) {
188        global $ID;
189
190        if($mode == 'xhtml' && !empty($data['current_params']['dataflt'])) {
191            $filters = $data['current_params']['dataflt'];
192            /** @var helper_plugin_struct_config $confHelper */
193            $confHelper = $this->loadHelper('struct_config');
194            $fltrs = array();
195            foreach($filters as $colcomp => $filter) {
196                $filter = $confHelper->parseFilterLine('', $colcomp.$filter);
197                if(strpos($filter[1], '~') !== false) {
198                    if(strpos($filter[1], '!~') !== false) {
199                        $comparator_value = '!~' . str_replace('%', '*', $filter[2]);
200                    } else {
201                        $comparator_value = '~' . str_replace('%', '', $filter[2]);
202                    }
203                    $fltrs[] = $filter[0] . $comparator_value;
204                } else {
205                    $fltrs[] = $filter[0] . $filter[1] . $filter[2];
206                }
207            }
208
209            $renderer->doc .= '<div class="filter">';
210            $renderer->doc .= '<h4>' . sprintf($this->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>';
211            $renderer->doc .= '<div class="resetfilter">';
212            $renderer->internallink($ID, $this->getLang('tableresetfilter'));
213            $renderer->doc .=  '</div>';
214            $renderer->doc .= '</div>';
215        }
216    }
217
218    /**
219     * @param string        $mode     the mode of the renderer
220     * @param Doku_Renderer $renderer the renderer
221     * @param array         $data     the configuration of the table/search
222     */
223    protected function addDynamicFilters($mode, Doku_Renderer $renderer, $data) {
224        if ($mode != 'xhtml') return;
225
226        global $conf, $ID;
227
228        $cur_params = $data['current_params'];
229        $html = '';
230        if($data['dynfilters']) {
231            $html .= '<tr class="dataflt">';
232
233            if($data['rownumbers']) {
234                $html .= '<th></th>';
235            }
236
237            foreach($data['headers'] as $num => $head) {
238                $html .= '<th>';
239                $form = new Doku_Form(array('method' => 'GET',));
240                $form->_hidden = array();
241                if(!$conf['userewrite']) {
242                    $form->addHidden('id', $ID);
243                }
244
245                $key = $data['cols'][$num] . '*~';
246                $val = isset($cur_params['dataflt'][$key]) ? $cur_params['dataflt'][$key] : '';
247
248                // Add current request params
249                if (!empty($cur_params['datasrt'])) {
250                    $form->addHidden('datasrt', $cur_params['datasrt']);
251                }
252                if (!empty($cur_params['dataofs'])) {
253                    $form->addHidden('dataofs', $cur_params['dataofs']);
254                }
255                foreach($cur_params['dataflt'] as $c_key => $c_val) {
256                    if($c_val !== '' && $c_key !== $key) {
257                        $form->addHidden('dataflt[' . $c_key . ']', $c_val);
258                    }
259                }
260
261                $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $val, ''));
262                $html .= $form->getForm();
263                $html .= '</th>';
264            }
265            $html .= '</tr>';
266            $renderer->doc .= $html;
267        }
268    }
269
270    /**
271     * @param string        $mode     the mode of the renderer
272     * @param Doku_Renderer $renderer the renderer
273     */
274    private function startTable($mode, Doku_Renderer $renderer) {
275        $renderer->table_open();
276    }
277
278    /**
279     * @param string $mode the mode of the renderer
280     * @param Doku_Renderer $renderer the renderer
281     * @param array $data the configuration of the table/search
282     * @param Column[] $cols
283     */
284    protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $data, $cols) {
285        global $ID;
286
287        $renderer->tablerow_open();
288
289        if($data['rownumbers']) {
290            $renderer->tableheader_open();
291            $renderer->cdata('#');
292            $renderer->tableheader_close();
293        }
294
295        foreach($data['headers'] as $num => $head) {
296            $ckey = $data['cols'][$num];
297            if(blank($head)) {
298                if(isset($cols[$num]) && is_a($cols[$num], 'plugin\struct\meta\Column')) {
299                    $head = $cols[$num]->getTranslatedLabel();
300                } else {
301                    $head = 'column '.$num; // this should never happen
302                }
303            }
304
305            $width = '';
306            if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') {
307                $width = ' style="width: ' . $data['widths'][$num] . ';"';
308            }
309            if ($mode == 'xhmtl') {
310                $renderer->doc .= '<th' . $width . '>';
311            } else {
312                $renderer->tableheader_open();
313            }
314
315            // add sort arrow
316            if ($mode == 'xhtml') {
317                if(isset($data['sort']) && $ckey == $data['sort'][0]) {
318                    if($data['sort'][1] == 'ASC') {
319                        $renderer->doc .= '<span>&darr;</span> ';
320                        $ckey = '^' . $ckey;
321                    } else {
322                        $renderer->doc .= '<span>&uarr;</span> ';
323                    }
324                }
325            }
326            $renderer->internallink($ID . "?" . http_build_query(array('datasrt' => $ckey,) + $data['current_params']), hsc($head));
327            $renderer->tableheader_close();
328        }
329        $renderer->tablerow_close();
330    }
331
332    /**
333     * @param string        $mode     the mode of the renderer
334     * @param Doku_Renderer $renderer the renderer
335     * @param string        $hash     hash to identify the table and group images in gallery
336     */
337    protected function startScope($mode, \Doku_Renderer $renderer, $hash) {
338        if ($mode == 'xhtml') {
339            $renderer->doc .= "<div class=\"table structaggregation\">";
340            $renderer->info['struct_table_hash'] = $hash;
341        }
342    }
343
344    /**
345     * if summarize was set, add sums
346     *
347     * @param string        $mode     the mode of the renderer
348     * @param Doku_Renderer $renderer the renderer
349     * @param array         $data     the configuration of the table/search
350     * @param array         $sums     the summarized output of the numerical fields
351     */
352    private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) {
353        if($data['summarize']) {
354            $renderer->tablerow_open();
355            $len = count($data['cols']);
356
357            if($data['rownumbers']) {
358                $renderer->tablecell_open();
359                $renderer->tablecell_close();
360            }
361
362            for($i = 0; $i < $len; $i++) {
363                $renderer->tablecell_open(1, $data['align'][$i]);
364                if(!empty($sums[$i])) {
365                    $renderer->cdata('∑ ' . $sums[$i]);
366                } else {
367                    if ($mode == 'xhtml') {
368                        $renderer->doc .= '&nbsp;';
369                    }
370                }
371                $renderer->tablecell_close();
372            }
373            $renderer->tablerow_close();
374        }
375    }
376
377    /**
378     * @param string        $mode     the mode of the renderer
379     * @param Doku_Renderer $renderer the renderer
380     *
381     */
382    private function finishTableAndScope($mode, Doku_Renderer $renderer) {
383        $renderer->table_close();
384        if ($mode == 'xhtml') {
385            $renderer->doc .= '</div>';
386            if(isset($renderer->info['struct_table_hash'])) {
387                unset($renderer->info['struct_table_hash']);
388            }
389        }
390    }
391
392    /**
393     * @param string        $mode     the mode of the renderer
394     * @param Doku_Renderer $renderer the renderer
395     * @param array         $data     the configuration of the table/search
396     * @param               $rows
397     *
398     */
399    private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) {
400        $renderer->tabletbody_open();
401        foreach($rows as $rownum => $row) {
402            $renderer->tablerow_open();
403
404            if($data['rownumbers']) {
405                $renderer->tablecell_open();
406                $renderer->doc .= $rownum + 1;
407                $renderer->tablecell_close();
408            }
409
410            /** @var plugin\struct\meta\Value $value */
411            foreach($row as $colnum => $value) {
412                $renderer->tablecell_open();
413                $value->render($renderer, $mode);
414                $renderer->tablecell_close();
415
416                // summarize
417                if($data['summarize'] && is_numeric($value->getValue())) {
418                    if(!isset($this->sums[$colnum])) {
419                        $this->sums[$colnum] = 0;
420                    }
421                    $this->sums[$colnum] += $value->getValue();
422                }
423            }
424            $renderer->tablerow_close();
425        }
426        $renderer->tabletbody_close();
427    }
428
429    /**
430     * @param array $data the configuration of the table/search
431     * @param string $mode the mode of the renderer
432     * @param Doku_Renderer $renderer the renderer
433     * @param Column[] $cols
434     */
435    private function nullList($data, $mode, Doku_Renderer $renderer, $cols) {
436        $this->renderPreTable($mode, $renderer, $data, $cols);
437        $renderer->tablerow_open();
438        $renderer->tablecell_open(count($data['cols']) + $data['rownumbers'], 'center');
439        $renderer->cdata($this->getLang('none'));
440        $renderer->tablecell_close();
441        $renderer->tablerow_close();
442        $renderer->table_close();
443    }
444}
445
446// vim:ts=4:sw=4:et:
447