xref: /plugin/struct/meta/AggregationTable.php (revision 986ab7e667ff90a7ae3648255fe5bc6c8f56601e)
107993756SAndreas Gohr<?php
207993756SAndreas Gohr
307993756SAndreas Gohrnamespace plugin\struct\meta;
407993756SAndreas Gohr
507993756SAndreas Gohrclass AggregationTable {
607993756SAndreas Gohr
707993756SAndreas Gohr    /**
807993756SAndreas Gohr     * @var string the page id of the page this is rendered to
907993756SAndreas Gohr     */
1007993756SAndreas Gohr    protected $id;
1107993756SAndreas Gohr    /**
1207993756SAndreas Gohr     * @var string the Type of renderer used
1307993756SAndreas Gohr     */
1407993756SAndreas Gohr    protected $mode;
1507993756SAndreas Gohr    /**
1607993756SAndreas Gohr     * @var \Doku_Renderer the DokuWiki renderer used to create the output
1707993756SAndreas Gohr     */
1807993756SAndreas Gohr    protected $renderer;
1907993756SAndreas Gohr    /**
2007993756SAndreas Gohr     * @var SearchConfig the configured search - gives access to columns etc.
2107993756SAndreas Gohr     */
2207993756SAndreas Gohr    protected $searchConfig;
2307993756SAndreas Gohr
2407993756SAndreas Gohr    /**
2507993756SAndreas Gohr     * @var Column[] the list of columns to be displayed
2607993756SAndreas Gohr     */
2707993756SAndreas Gohr    protected $columns;
2807993756SAndreas Gohr
2907993756SAndreas Gohr    /**
3007993756SAndreas Gohr     * @var  Value[][] the search result
3107993756SAndreas Gohr     */
3207993756SAndreas Gohr    protected $result;
3307993756SAndreas Gohr
3407993756SAndreas Gohr    /**
3507993756SAndreas Gohr     * @var int number of all results
3607993756SAndreas Gohr     */
3707993756SAndreas Gohr    protected $resultCount;
3807993756SAndreas Gohr
3907993756SAndreas Gohr    /**
4007993756SAndreas Gohr     * @var array for summing up columns
4107993756SAndreas Gohr     */
4207993756SAndreas Gohr    protected $sums;
4307993756SAndreas Gohr
4407993756SAndreas Gohr    /**
4507993756SAndreas Gohr     * @todo we might be able to get rid of this helper and move this to SearchConfig
4607993756SAndreas Gohr     * @var \helper_plugin_struct_config
4707993756SAndreas Gohr     */
4807993756SAndreas Gohr    protected $helper;
4907993756SAndreas Gohr
5007993756SAndreas Gohr    /**
5107993756SAndreas Gohr     * Initialize the Aggregation renderer and executes the search
5207993756SAndreas Gohr     *
5307993756SAndreas Gohr     * You need to call @see render() on the resulting object.
5407993756SAndreas Gohr     *
5507993756SAndreas Gohr     * @param string $id
5607993756SAndreas Gohr     * @param string $mode
5707993756SAndreas Gohr     * @param \Doku_Renderer $renderer
5807993756SAndreas Gohr     * @param SearchConfig $searchConfig
5907993756SAndreas Gohr     */
6007993756SAndreas Gohr    public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig) {
6107993756SAndreas Gohr        $this->id = $id;
6207993756SAndreas Gohr        $this->mode = $mode;
6307993756SAndreas Gohr        $this->renderer = $renderer;
6407993756SAndreas Gohr        $this->searchConfig = $searchConfig;
6507993756SAndreas Gohr        $this->data = $searchConfig->getConf();
6607993756SAndreas Gohr        $this->columns = $searchConfig->getColumns();
6707993756SAndreas Gohr
6807993756SAndreas Gohr        $this->result = $this->searchConfig->execute();
6907993756SAndreas Gohr        $this->resultCount = $this->searchConfig->getCount();
7007993756SAndreas Gohr        $this->helper = plugin_load('helper', 'struct_config');
7107993756SAndreas Gohr    }
7207993756SAndreas Gohr
7307993756SAndreas Gohr    /**
7407993756SAndreas Gohr     * Create the table on the renderer
7507993756SAndreas Gohr     */
7607993756SAndreas Gohr    public function render() {
7707993756SAndreas Gohr        // table open
7807993756SAndreas Gohr        $this->startScope();
79*986ab7e6SAndreas Gohr        $this->renderActiveFilters();
8007993756SAndreas Gohr        $this->renderer->table_open();
8107993756SAndreas Gohr
8207993756SAndreas Gohr        // header
8307993756SAndreas Gohr        $this->renderer->tablethead_open();
84*986ab7e6SAndreas Gohr        $this->renderColumnHeaders();
85*986ab7e6SAndreas Gohr        $this->renderDynamicFilters();
8607993756SAndreas Gohr        $this->renderer->tablethead_close();
8707993756SAndreas Gohr
8807993756SAndreas Gohr        if($this->resultCount) {
8907993756SAndreas Gohr            // actual data
90*986ab7e6SAndreas Gohr            $this->renderResult();
9107993756SAndreas Gohr
9207993756SAndreas Gohr            // footer
93*986ab7e6SAndreas Gohr            $this->renderSums();
94*986ab7e6SAndreas Gohr            $this->renderPagingControls();
9507993756SAndreas Gohr        } else {
9607993756SAndreas Gohr            // nothing found
97*986ab7e6SAndreas Gohr            $this->renderEmptyResult();
9807993756SAndreas Gohr        }
9907993756SAndreas Gohr
10007993756SAndreas Gohr        // table close
10107993756SAndreas Gohr        $this->renderer->table_close();
10207993756SAndreas Gohr        $this->finishScope();
10307993756SAndreas Gohr    }
10407993756SAndreas Gohr
10507993756SAndreas Gohr    /**
10607993756SAndreas Gohr     * Adds additional info to document and renderer in XHTML mode
10707993756SAndreas Gohr     *
10807993756SAndreas Gohr     * @see finishScope()
10907993756SAndreas Gohr     */
11007993756SAndreas Gohr    protected function startScope() {
11107993756SAndreas Gohr        if($this->mode != 'xhtml') return;
11207993756SAndreas Gohr
11307993756SAndreas Gohr        // wrapping div
11407993756SAndreas Gohr        $this->renderer->doc .= "<div class=\"structaggregation\">";
11507993756SAndreas Gohr
11607993756SAndreas Gohr        // unique identifier for this aggregation
11707993756SAndreas Gohr        $this->renderer->info['struct_table_hash'] = md5(var_export($this->data, true));
11807993756SAndreas Gohr    }
11907993756SAndreas Gohr
12007993756SAndreas Gohr    /**
12107993756SAndreas Gohr     * Closes the table and anything opened in startScope()
12207993756SAndreas Gohr     *
12307993756SAndreas Gohr     * @see startScope()
12407993756SAndreas Gohr     */
12507993756SAndreas Gohr    protected function finishScope() {
12607993756SAndreas Gohr        if($this->mode != 'xhtml') return;
12707993756SAndreas Gohr
12807993756SAndreas Gohr        // wrapping div
12907993756SAndreas Gohr        $this->renderer->doc .= '</div>';
13007993756SAndreas Gohr
13107993756SAndreas Gohr        // remove identifier from renderer again
13207993756SAndreas Gohr        if(isset($this->renderer->info['struct_table_hash'])) {
13307993756SAndreas Gohr            unset($this->renderer->info['struct_table_hash']);
13407993756SAndreas Gohr        }
13507993756SAndreas Gohr    }
13607993756SAndreas Gohr
13707993756SAndreas Gohr    /**
13807993756SAndreas Gohr     * Displays info about the currently applied filters
13907993756SAndreas Gohr     */
140*986ab7e6SAndreas Gohr    protected function renderActiveFilters() {
14107993756SAndreas Gohr        if($this->mode != 'xhtml') return;
14207993756SAndreas Gohr        $dynamic = $this->searchConfig->getDynamicParameters();
14307993756SAndreas Gohr        $filters = $dynamic->getFilters();
14407993756SAndreas Gohr        if(!$filters) return;
14507993756SAndreas Gohr
14607993756SAndreas Gohr        $fltrs = array();
14707993756SAndreas Gohr        foreach($filters as $column => $filter) {
14807993756SAndreas Gohr            list($comp, $value) = $filter;
14907993756SAndreas Gohr
15007993756SAndreas Gohr            if(strpos($comp, '~') !== false) {
15107993756SAndreas Gohr                if(strpos($comp, '!~') !== false) {
15207993756SAndreas Gohr                    $comparator_value = '!~' . str_replace('%', '*', $value);
15307993756SAndreas Gohr                } else {
15407993756SAndreas Gohr                    $comparator_value = '~' . str_replace('%', '', $value);
15507993756SAndreas Gohr                }
15607993756SAndreas Gohr                $fltrs[] = $column . $comparator_value;
15707993756SAndreas Gohr            } else {
15807993756SAndreas Gohr                $fltrs[] = $column . $comp . $value;
15907993756SAndreas Gohr            }
16007993756SAndreas Gohr        }
16107993756SAndreas Gohr
16207993756SAndreas Gohr        $this->renderer->doc .= '<div class="filter">';
16307993756SAndreas Gohr        $this->renderer->doc .= '<h4>' . sprintf($this->helper->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>';
16407993756SAndreas Gohr        $this->renderer->doc .= '<div class="resetfilter">';
16507993756SAndreas Gohr        $this->renderer->internallink($this->id, $this->helper->getLang('tableresetfilter'));
16607993756SAndreas Gohr        $this->renderer->doc .= '</div>';
16707993756SAndreas Gohr        $this->renderer->doc .= '</div>';
16807993756SAndreas Gohr    }
16907993756SAndreas Gohr
17007993756SAndreas Gohr    /**
17107993756SAndreas Gohr     * Shows the column headers with links to sort by column
17207993756SAndreas Gohr     */
173*986ab7e6SAndreas Gohr    protected function renderColumnHeaders() {
17407993756SAndreas Gohr        $this->renderer->tablerow_open();
17507993756SAndreas Gohr
17607993756SAndreas Gohr        // additional column for row numbers
17707993756SAndreas Gohr        if($this->data['rownumbers']) {
17807993756SAndreas Gohr            $this->renderer->tableheader_open();
17907993756SAndreas Gohr            $this->renderer->cdata('#');
18007993756SAndreas Gohr            $this->renderer->tableheader_close();
18107993756SAndreas Gohr        }
18207993756SAndreas Gohr
18307993756SAndreas Gohr        // show all headers
18407993756SAndreas Gohr        foreach($this->data['headers'] as $num => $header) {
18507993756SAndreas Gohr            $column = $this->columns[$num];
18607993756SAndreas Gohr
18707993756SAndreas Gohr            // use field label if no header was set
18807993756SAndreas Gohr            if(blank($header)) {
18907993756SAndreas Gohr                if(is_a($column, 'plugin\struct\meta\PageColumn')) {
19007993756SAndreas Gohr                    $header = $this->helper->getLang('pagelabel'); // @todo this could be part of PageColumn::getTranslatedLabel
19107993756SAndreas Gohr                } else if(is_a($column, 'plugin\struct\meta\Column')) {
19207993756SAndreas Gohr                    $header = $column->getTranslatedLabel();
19307993756SAndreas Gohr                } else {
19407993756SAndreas Gohr                    $header = 'column ' . $num; // this should never happen
19507993756SAndreas Gohr                }
19607993756SAndreas Gohr            }
19707993756SAndreas Gohr
19807993756SAndreas Gohr            // simple mode first
19907993756SAndreas Gohr            if($this->mode != 'xhtml') {
20007993756SAndreas Gohr                $this->renderer->tableheader_open();
20107993756SAndreas Gohr                $this->renderer->cdata($header);
20207993756SAndreas Gohr                $this->renderer->tableheader_close();
20307993756SAndreas Gohr                continue;
20407993756SAndreas Gohr            }
20507993756SAndreas Gohr
20607993756SAndreas Gohr            // still here? create custom header for more flexibility
20707993756SAndreas Gohr
20807993756SAndreas Gohr            // width setting
20907993756SAndreas Gohr            $width = '';
21007993756SAndreas Gohr            if(isset($data['widths'][$num]) && $data['widths'][$num] != '-') {
21107993756SAndreas Gohr                $width = ' style="width: ' . $data['widths'][$num] . ';"';
21207993756SAndreas Gohr            }
21307993756SAndreas Gohr
21407993756SAndreas Gohr            // sort indicator and link
21507993756SAndreas Gohr            $sortclass = '';
21607993756SAndreas Gohr            $sorts = $this->searchConfig->getSorts();
21707993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
21807993756SAndreas Gohr            if(isset($sorts[$column->getFullQualifiedLabel()])) {
21907993756SAndreas Gohr                list(, $currentSort) = $sorts[$column->getFullQualifiedLabel()];
22007993756SAndreas Gohr                if($currentSort[1]) {
22107993756SAndreas Gohr                    $sortclass = 'sort-down';
22207993756SAndreas Gohr                    $dynamic->setSort($column, false);
22307993756SAndreas Gohr                } else {
22407993756SAndreas Gohr                    $sortclass = 'sort-up';
22507993756SAndreas Gohr                }
22607993756SAndreas Gohr            }
22707993756SAndreas Gohr            $dynamic->setSort($column, true);
22807993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
22907993756SAndreas Gohr
23007993756SAndreas Gohr            // output XHTML header
23107993756SAndreas Gohr            $this->renderer->doc .= "<th $width >";
23207993756SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="' . $sortclass . '" title="' . $this->helper->getLang('sort') . '">' . hsc($header) . '</a>';
23307993756SAndreas Gohr            $this->renderer->doc .= '</th>';
23407993756SAndreas Gohr        }
23507993756SAndreas Gohr
23607993756SAndreas Gohr        $this->renderer->tablerow_close();
23707993756SAndreas Gohr    }
23807993756SAndreas Gohr
23907993756SAndreas Gohr    /**
24007993756SAndreas Gohr     * Add input fields for dynamic filtering
24107993756SAndreas Gohr     */
242*986ab7e6SAndreas Gohr    protected function renderDynamicFilters() {
24307993756SAndreas Gohr        if($this->mode != 'xhtml') return;
24407993756SAndreas Gohr        if(!$this->data['dynfilters']) return;
24507993756SAndreas Gohr
24607993756SAndreas Gohr        $this->renderer->doc .= '<tr class="dataflt">';
24707993756SAndreas Gohr
24807993756SAndreas Gohr        // add extra column for row numbers
24907993756SAndreas Gohr        if($this->data['rownumbers']) {
25007993756SAndreas Gohr            $this->renderer->doc .= '<th></th>';
25107993756SAndreas Gohr        }
25207993756SAndreas Gohr
25307993756SAndreas Gohr        // each column gets a form
25407993756SAndreas Gohr        foreach($this->columns as $column) {
25507993756SAndreas Gohr            $this->renderer->doc .= '<th>';
25607993756SAndreas Gohr            {
25707993756SAndreas Gohr                $form = new \Doku_Form(array('method' => 'GET', 'action' => wl($this->id)));
25807993756SAndreas Gohr
25907993756SAndreas Gohr                // current value
26007993756SAndreas Gohr                $dynamic = $this->searchConfig->getDynamicParameters();
26107993756SAndreas Gohr                $filters = $dynamic->getFilters();
26207993756SAndreas Gohr                if(isset($filters[$column->getFullQualifiedLabel()])) {
26307993756SAndreas Gohr                    list(, $current) = $filters[$column->getFullQualifiedLabel()];
26407993756SAndreas Gohr                    $dynamic->removeFilter($column);
26507993756SAndreas Gohr                } else {
26607993756SAndreas Gohr                    $current = '';
26707993756SAndreas Gohr                }
26807993756SAndreas Gohr
26907993756SAndreas Gohr                // Add current request params
27007993756SAndreas Gohr                $params = $dynamic->getURLParameters();
27107993756SAndreas Gohr                foreach($params as $key => $val) {
27207993756SAndreas Gohr                    $form->addHidden($key, $val);
27307993756SAndreas Gohr                }
27407993756SAndreas Gohr
27507993756SAndreas Gohr                // add input field
27607993756SAndreas Gohr                $key = $column->getFullQualifiedLabel() . '*~';
27707993756SAndreas Gohr                $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $current, ''));
27807993756SAndreas Gohr                $this->renderer->doc .= $form->getForm();
27907993756SAndreas Gohr            }
28007993756SAndreas Gohr            $this->renderer->doc .= '</th>';
28107993756SAndreas Gohr        }
28207993756SAndreas Gohr        $this->renderer->doc .= '</tr>';
28307993756SAndreas Gohr
28407993756SAndreas Gohr    }
28507993756SAndreas Gohr
28607993756SAndreas Gohr    /**
28707993756SAndreas Gohr     * Display the actual table data
28807993756SAndreas Gohr     */
289*986ab7e6SAndreas Gohr    protected function renderResult() {
29007993756SAndreas Gohr        $this->renderer->tabletbody_open();
29107993756SAndreas Gohr        foreach($this->result as $rownum => $row) {
29207993756SAndreas Gohr            $this->renderer->tablerow_open();
29307993756SAndreas Gohr
29407993756SAndreas Gohr            // row number column
29507993756SAndreas Gohr            if($this->data['rownumbers']) {
29607993756SAndreas Gohr                $this->renderer->tablecell_open();
29707993756SAndreas Gohr                $this->renderer->doc .= $rownum + 1;
29807993756SAndreas Gohr                $this->renderer->tablecell_close();
29907993756SAndreas Gohr            }
30007993756SAndreas Gohr
30107993756SAndreas Gohr            /** @var Value $value */
30207993756SAndreas Gohr            foreach($row as $colnum => $value) {
30307993756SAndreas Gohr                $this->renderer->tablecell_open();
30407993756SAndreas Gohr                $value->render($this->renderer, $this->mode);
30507993756SAndreas Gohr                $this->renderer->tablecell_close();
30607993756SAndreas Gohr
30707993756SAndreas Gohr                // summarize
30807993756SAndreas Gohr                if($this->data['summarize'] && is_numeric($value->getValue())) {
30907993756SAndreas Gohr                    if(!isset($this->sums[$colnum])) {
31007993756SAndreas Gohr                        $this->sums[$colnum] = 0;
31107993756SAndreas Gohr                    }
31207993756SAndreas Gohr                    $this->sums[$colnum] += $value->getValue();
31307993756SAndreas Gohr                }
31407993756SAndreas Gohr            }
31507993756SAndreas Gohr            $this->renderer->tablerow_close();
31607993756SAndreas Gohr        }
31707993756SAndreas Gohr        $this->renderer->tabletbody_close();
31807993756SAndreas Gohr    }
31907993756SAndreas Gohr
32007993756SAndreas Gohr    /**
32107993756SAndreas Gohr     * Renders an information row for when no results were found
32207993756SAndreas Gohr     */
323*986ab7e6SAndreas Gohr    protected function renderEmptyResult() {
32407993756SAndreas Gohr        $this->renderer->tablerow_open();
32507993756SAndreas Gohr        $this->renderer->tablecell_open(count($this->data['cols']) + $this->data['rownumbers'], 'center');
32607993756SAndreas Gohr        $this->renderer->cdata($this->helper->getLang('none'));
32707993756SAndreas Gohr        $this->renderer->tablecell_close();
32807993756SAndreas Gohr        $this->renderer->tablerow_close();
32907993756SAndreas Gohr    }
33007993756SAndreas Gohr
33107993756SAndreas Gohr    /**
33207993756SAndreas Gohr     * Add sums if wanted
33307993756SAndreas Gohr     */
334*986ab7e6SAndreas Gohr    protected function renderSums() {
33507993756SAndreas Gohr        if($this->data['summarize']) return;
33607993756SAndreas Gohr
33707993756SAndreas Gohr        $this->renderer->tablerow_open();
33807993756SAndreas Gohr        $len = count($this->data['cols']);
33907993756SAndreas Gohr
34007993756SAndreas Gohr        if($this->data['rownumbers']) {
34107993756SAndreas Gohr            $this->renderer->tablecell_open();
34207993756SAndreas Gohr            $this->renderer->tablecell_close();
34307993756SAndreas Gohr        }
34407993756SAndreas Gohr
34507993756SAndreas Gohr        for($i = 0; $i < $len; $i++) {
34607993756SAndreas Gohr            $this->renderer->tablecell_open(1, $this->data['align'][$i]);
34707993756SAndreas Gohr            if(!empty($sums[$i])) {
34807993756SAndreas Gohr                $this->renderer->cdata('∑ ' . $sums[$i]);
34907993756SAndreas Gohr            } else {
35007993756SAndreas Gohr                if($this->mode == 'xhtml') {
35107993756SAndreas Gohr                    $this->renderer->doc .= '&nbsp;';
35207993756SAndreas Gohr                }
35307993756SAndreas Gohr            }
35407993756SAndreas Gohr            $this->renderer->tablecell_close();
35507993756SAndreas Gohr        }
35607993756SAndreas Gohr        $this->renderer->tablerow_close();
35707993756SAndreas Gohr    }
35807993756SAndreas Gohr
35907993756SAndreas Gohr    /**
360*986ab7e6SAndreas Gohr     * Adds paging controls to the table
36107993756SAndreas Gohr     */
362*986ab7e6SAndreas Gohr    protected function renderPagingControls() {
36307993756SAndreas Gohr        if(empty($this->data['limit'])) return;
36407993756SAndreas Gohr        if($this->mode != 'xhtml') ;
36507993756SAndreas Gohr
36607993756SAndreas Gohr        $this->renderer->tablerow_open();
36707993756SAndreas Gohr        $this->renderer->tableheader_open((count($this->data['cols']) + ($this->data['rownumbers'] ? 1 : 0)));
36807993756SAndreas Gohr        $offset = $this->data['offset'];
36907993756SAndreas Gohr
37007993756SAndreas Gohr        // prev link
37107993756SAndreas Gohr        if($offset) {
37207993756SAndreas Gohr            $prev = $offset - $this->data['limit'];
37307993756SAndreas Gohr            if($prev < 0) {
37407993756SAndreas Gohr                $prev = 0;
37507993756SAndreas Gohr            }
37607993756SAndreas Gohr
37707993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
37807993756SAndreas Gohr            $dynamic->setOffset($prev);
37907993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
38007993756SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="prev">' . $this->helper->getLang('prev') . '</a>';
38107993756SAndreas Gohr        }
38207993756SAndreas Gohr
38307993756SAndreas Gohr        // next link
38407993756SAndreas Gohr        if($this->resultCount > $offset + $this->data['limit']) {
38507993756SAndreas Gohr            $next = $offset + $this->data['limit'];
38607993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
38707993756SAndreas Gohr            $dynamic->setOffset($next);
38807993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
38907993756SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="next">' . $this->helper->getLang('next') . '</a>';
39007993756SAndreas Gohr        }
39107993756SAndreas Gohr
39207993756SAndreas Gohr        $this->renderer->tableheader_close();
39307993756SAndreas Gohr        $this->renderer->tablerow_close();
39407993756SAndreas Gohr    }
39507993756SAndreas Gohr}
396