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