xref: /plugin/struct/meta/AggregationTable.php (revision 17a3a5782666ca8742a2c64cc11565d4f9fe1076)
107993756SAndreas Gohr<?php
207993756SAndreas Gohr
3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
407993756SAndreas Gohr
5d60f71efSAndreas Gohr/**
6d60f71efSAndreas Gohr * Creates the table aggregation output
7d60f71efSAndreas Gohr *
8ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta
9d60f71efSAndreas Gohr */
10d6d97f60SAnna Dabrowskaclass AggregationTable
11d6d97f60SAnna Dabrowska{
1207993756SAndreas Gohr    /**
1307993756SAndreas Gohr     * @var string the page id of the page this is rendered to
1407993756SAndreas Gohr     */
1507993756SAndreas Gohr    protected $id;
1607993756SAndreas Gohr    /**
1707993756SAndreas Gohr     * @var string the Type of renderer used
1807993756SAndreas Gohr     */
1907993756SAndreas Gohr    protected $mode;
2007993756SAndreas Gohr    /**
2107993756SAndreas Gohr     * @var \Doku_Renderer the DokuWiki renderer used to create the output
2207993756SAndreas Gohr     */
2307993756SAndreas Gohr    protected $renderer;
2407993756SAndreas Gohr    /**
2507993756SAndreas Gohr     * @var SearchConfig the configured search - gives access to columns etc.
2607993756SAndreas Gohr     */
2707993756SAndreas Gohr    protected $searchConfig;
2807993756SAndreas Gohr
2907993756SAndreas Gohr    /**
3007993756SAndreas Gohr     * @var Column[] the list of columns to be displayed
3107993756SAndreas Gohr     */
3207993756SAndreas Gohr    protected $columns;
3307993756SAndreas Gohr
3407993756SAndreas Gohr    /**
3507993756SAndreas Gohr     * @var  Value[][] the search result
3607993756SAndreas Gohr     */
3707993756SAndreas Gohr    protected $result;
3807993756SAndreas Gohr
3907993756SAndreas Gohr    /**
4007993756SAndreas Gohr     * @var int number of all results
4107993756SAndreas Gohr     */
4207993756SAndreas Gohr    protected $resultCount;
4307993756SAndreas Gohr
4407993756SAndreas Gohr    /**
45d4b5a17cSAndreas Gohr     * @var string[] the result PIDs for each row
46d4b5a17cSAndreas Gohr     */
47d4b5a17cSAndreas Gohr    protected $resultPIDs;
480ceefd5cSAnna Dabrowska    protected $resultRids;
496fd73b4bSAnna Dabrowska    protected $resultRevs;
50d4b5a17cSAndreas Gohr
51d4b5a17cSAndreas Gohr    /**
5207993756SAndreas Gohr     * @var array for summing up columns
5307993756SAndreas Gohr     */
5407993756SAndreas Gohr    protected $sums;
5507993756SAndreas Gohr
5607993756SAndreas Gohr    /**
576b5e52fdSAndreas Gohr     * @var bool skip full table when no results found
586b5e52fdSAndreas Gohr     */
596b5e52fdSAndreas Gohr    protected $simplenone = true;
606b5e52fdSAndreas Gohr
616b5e52fdSAndreas Gohr    /**
6207993756SAndreas Gohr     * @todo we might be able to get rid of this helper and move this to SearchConfig
6307993756SAndreas Gohr     * @var \helper_plugin_struct_config
6407993756SAndreas Gohr     */
6507993756SAndreas Gohr    protected $helper;
6607993756SAndreas Gohr
6707993756SAndreas Gohr    /**
6807993756SAndreas Gohr     * Initialize the Aggregation renderer and executes the search
6907993756SAndreas Gohr     *
700549dcc5SAndreas Gohr     * You need to call @param string $id
7107993756SAndreas Gohr     * @param string $mode
7207993756SAndreas Gohr     * @param \Doku_Renderer $renderer
7307993756SAndreas Gohr     * @param SearchConfig $searchConfig
740549dcc5SAndreas Gohr     * @see render() on the resulting object.
750549dcc5SAndreas Gohr     *
7607993756SAndreas Gohr     */
778ce43f5aSAnna Dabrowska    public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig)
78d6d97f60SAnna Dabrowska    {
7907993756SAndreas Gohr        $this->id = $id;
8007993756SAndreas Gohr        $this->mode = $mode;
8107993756SAndreas Gohr        $this->renderer = $renderer;
8207993756SAndreas Gohr        $this->searchConfig = $searchConfig;
8307993756SAndreas Gohr        $this->data = $searchConfig->getConf();
8407993756SAndreas Gohr        $this->columns = $searchConfig->getColumns();
8507993756SAndreas Gohr
868ce43f5aSAnna Dabrowska        $this->result = $this->searchConfig->execute();
8707993756SAndreas Gohr        $this->resultCount = $this->searchConfig->getCount();
88d4b5a17cSAndreas Gohr        $this->resultPIDs = $this->searchConfig->getPids();
890ceefd5cSAnna Dabrowska        $this->resultRids = $this->searchConfig->getRids();
906fd73b4bSAnna Dabrowska        $this->resultRevs = $this->searchConfig->getRevs();
9107993756SAndreas Gohr        $this->helper = plugin_load('helper', 'struct_config');
9207993756SAndreas Gohr    }
9307993756SAndreas Gohr
9407993756SAndreas Gohr    /**
95844a4f01SFrieder Schrempf     * Returns the page id for the table
96844a4f01SFrieder Schrempf     */
97844a4f01SFrieder Schrempf    public function getID()
98844a4f01SFrieder Schrempf    {
99844a4f01SFrieder Schrempf        return $this->id;
100844a4f01SFrieder Schrempf    }
101844a4f01SFrieder Schrempf
102844a4f01SFrieder Schrempf    /**
10307993756SAndreas Gohr     * Create the table on the renderer
10407993756SAndreas Gohr     */
105d6d97f60SAnna Dabrowska    public function render()
106d6d97f60SAnna Dabrowska    {
107b7e1d73bSAndreas Gohr
108b7e1d73bSAndreas Gohr        // abort early if there are no results at all (not filtered)
1096b5e52fdSAndreas Gohr        if (!$this->resultCount && !$this->isDynamicallyFiltered() && $this->simplenone) {
110b7e1d73bSAndreas Gohr            $this->startScope();
111b7e1d73bSAndreas Gohr            $this->renderer->cdata($this->helper->getLang('none'));
112b7e1d73bSAndreas Gohr            $this->finishScope();
113b7e1d73bSAndreas Gohr            return;
114b7e1d73bSAndreas Gohr        }
115b7e1d73bSAndreas Gohr
11607993756SAndreas Gohr        $this->startScope();
117986ab7e6SAndreas Gohr        $this->renderActiveFilters();
118844a4f01SFrieder Schrempf
119844a4f01SFrieder Schrempf        $rendercontext = array(
120844a4f01SFrieder Schrempf            'table' => $this,
121844a4f01SFrieder Schrempf            'renderer' => $this->renderer,
1223f640228SFrieder Schrempf            'format' => $this->mode,
123844a4f01SFrieder Schrempf            'search' => $this->searchConfig,
124844a4f01SFrieder Schrempf            'columns' => $this->columns,
125844a4f01SFrieder Schrempf            'data' => $this->result
126844a4f01SFrieder Schrempf        );
127844a4f01SFrieder Schrempf
1282dbe71f8SAnna Dabrowska        $event = new \Doku_Event(
129844a4f01SFrieder Schrempf            'PLUGIN_STRUCT_RENDER_AGGREGATION_TABLE',
1302dbe71f8SAnna Dabrowska            $rendercontext
131844a4f01SFrieder Schrempf        );
1322dbe71f8SAnna Dabrowska        $event->trigger([$this, 'renderTable']);
133844a4f01SFrieder Schrempf
134844a4f01SFrieder Schrempf        // export handle
135844a4f01SFrieder Schrempf        $this->renderExportControls();
136844a4f01SFrieder Schrempf        $this->finishScope();
137844a4f01SFrieder Schrempf    }
138844a4f01SFrieder Schrempf
139844a4f01SFrieder Schrempf    /**
140844a4f01SFrieder Schrempf     * Render the default aggregation table
141844a4f01SFrieder Schrempf     */
142844a4f01SFrieder Schrempf    public function renderTable($rendercontext)
143844a4f01SFrieder Schrempf    {
14407993756SAndreas Gohr        $this->renderer->table_open();
14507993756SAndreas Gohr
14607993756SAndreas Gohr        // header
14707993756SAndreas Gohr        $this->renderer->tablethead_open();
148986ab7e6SAndreas Gohr        $this->renderColumnHeaders();
149986ab7e6SAndreas Gohr        $this->renderDynamicFilters();
15007993756SAndreas Gohr        $this->renderer->tablethead_close();
15107993756SAndreas Gohr
15207993756SAndreas Gohr        if ($this->resultCount) {
15307993756SAndreas Gohr            // actual data
154a9fd81f9SAndreas Gohr            $this->renderer->tabletbody_open();
155986ab7e6SAndreas Gohr            $this->renderResult();
156a9fd81f9SAndreas Gohr            $this->renderer->tabletbody_close();
15707993756SAndreas Gohr
158a9fd81f9SAndreas Gohr            // footer (tfoot is develonly currently)
159a9fd81f9SAndreas Gohr            if (method_exists($this->renderer, 'tabletfoot_open')) $this->renderer->tabletfoot_open();
160986ab7e6SAndreas Gohr            $this->renderSums();
161986ab7e6SAndreas Gohr            $this->renderPagingControls();
162a9fd81f9SAndreas Gohr            if (method_exists($this->renderer, 'tabletfoot_close')) $this->renderer->tabletfoot_close();
16307993756SAndreas Gohr        } else {
16407993756SAndreas Gohr            // nothing found
165986ab7e6SAndreas Gohr            $this->renderEmptyResult();
16607993756SAndreas Gohr        }
16707993756SAndreas Gohr
16807993756SAndreas Gohr        // table close
16907993756SAndreas Gohr        $this->renderer->table_close();
17007993756SAndreas Gohr    }
17107993756SAndreas Gohr
17207993756SAndreas Gohr    /**
17307993756SAndreas Gohr     * Adds additional info to document and renderer in XHTML mode
17407993756SAndreas Gohr     *
17507993756SAndreas Gohr     * @see finishScope()
17607993756SAndreas Gohr     */
177d6d97f60SAnna Dabrowska    protected function startScope()
178d6d97f60SAnna Dabrowska    {
17907993756SAndreas Gohr        // unique identifier for this aggregation
18007993756SAndreas Gohr        $this->renderer->info['struct_table_hash'] = md5(var_export($this->data, true));
18109dd691aSAndreas Gohr
18209dd691aSAndreas Gohr        // wrapping div
18309dd691aSAndreas Gohr        if ($this->mode != 'xhtml') return;
18409dd691aSAndreas Gohr        $this->renderer->doc .= "<div class=\"structaggregation\">";
18507993756SAndreas Gohr    }
18607993756SAndreas Gohr
18707993756SAndreas Gohr    /**
18807993756SAndreas Gohr     * Closes the table and anything opened in startScope()
18907993756SAndreas Gohr     *
19007993756SAndreas Gohr     * @see startScope()
19107993756SAndreas Gohr     */
192d6d97f60SAnna Dabrowska    protected function finishScope()
193d6d97f60SAnna Dabrowska    {
19407993756SAndreas Gohr        // remove identifier from renderer again
19507993756SAndreas Gohr        if (isset($this->renderer->info['struct_table_hash'])) {
19607993756SAndreas Gohr            unset($this->renderer->info['struct_table_hash']);
19707993756SAndreas Gohr        }
19809dd691aSAndreas Gohr
19909dd691aSAndreas Gohr        // wrapping div
20009dd691aSAndreas Gohr        if ($this->mode != 'xhtml') return;
20109dd691aSAndreas Gohr        $this->renderer->doc .= '</div>';
20207993756SAndreas Gohr    }
20307993756SAndreas Gohr
20407993756SAndreas Gohr    /**
20507993756SAndreas Gohr     * Displays info about the currently applied filters
20607993756SAndreas Gohr     */
207d6d97f60SAnna Dabrowska    protected function renderActiveFilters()
208d6d97f60SAnna Dabrowska    {
20907993756SAndreas Gohr        if ($this->mode != 'xhtml') return;
21007993756SAndreas Gohr        $dynamic = $this->searchConfig->getDynamicParameters();
21107993756SAndreas Gohr        $filters = $dynamic->getFilters();
21207993756SAndreas Gohr        if (!$filters) return;
21307993756SAndreas Gohr
21407993756SAndreas Gohr        $fltrs = array();
21507993756SAndreas Gohr        foreach ($filters as $column => $filter) {
21607993756SAndreas Gohr            list($comp, $value) = $filter;
21704ec4785SAnna Dabrowska
21804ec4785SAnna Dabrowska            // display the filters in a human readable format
21904ec4785SAnna Dabrowska            foreach ($this->columns as $col) {
22004ec4785SAnna Dabrowska                if ($column === $col->getFullQualifiedLabel()) {
22104ec4785SAnna Dabrowska                    $column = $col->getTranslatedLabel();
22204ec4785SAnna Dabrowska                }
22304ec4785SAnna Dabrowska            }
2241f075418SAnna Dabrowska            $fltrs[] = sprintf('"%s" %s "%s"', $column, $this->helper->getLang("comparator $comp"), $value);
22507993756SAndreas Gohr        }
22607993756SAndreas Gohr
22707993756SAndreas Gohr        $this->renderer->doc .= '<div class="filter">';
228*17a3a578SAndreas Gohr        $this->renderer->doc .= '<h4>' .
229*17a3a578SAndreas Gohr            sprintf(
230*17a3a578SAndreas Gohr                $this->helper->getLang('tablefilteredby'),
231*17a3a578SAndreas Gohr                hsc(implode(' & ', $fltrs))
232*17a3a578SAndreas Gohr            ) .
233*17a3a578SAndreas Gohr            '</h4>';
23407993756SAndreas Gohr        $this->renderer->doc .= '<div class="resetfilter">';
23507993756SAndreas Gohr        $this->renderer->internallink($this->id, $this->helper->getLang('tableresetfilter'));
23607993756SAndreas Gohr        $this->renderer->doc .= '</div>';
23707993756SAndreas Gohr        $this->renderer->doc .= '</div>';
23807993756SAndreas Gohr    }
23907993756SAndreas Gohr
24007993756SAndreas Gohr    /**
24107993756SAndreas Gohr     * Shows the column headers with links to sort by column
24207993756SAndreas Gohr     */
243d6d97f60SAnna Dabrowska    protected function renderColumnHeaders()
244d6d97f60SAnna Dabrowska    {
24507993756SAndreas Gohr        $this->renderer->tablerow_open();
24607993756SAndreas Gohr
24707993756SAndreas Gohr        // additional column for row numbers
24834ea6e10SAnna Dabrowska        if (!empty($this->data['rownumbers'])) {
24907993756SAndreas Gohr            $this->renderer->tableheader_open();
25007993756SAndreas Gohr            $this->renderer->cdata('#');
25107993756SAndreas Gohr            $this->renderer->tableheader_close();
25207993756SAndreas Gohr        }
25307993756SAndreas Gohr
25407993756SAndreas Gohr        // show all headers
2558c4ee9beSAndreas Gohr        foreach ($this->columns as $num => $column) {
2568c4ee9beSAndreas Gohr            $header = '';
2578c4ee9beSAndreas Gohr            if (isset($this->data['headers'][$num])) {
2588c4ee9beSAndreas Gohr                $header = $this->data['headers'][$num];
2598c4ee9beSAndreas Gohr            }
26007993756SAndreas Gohr
26107993756SAndreas Gohr            // use field label if no header was set
26207993756SAndreas Gohr            if (blank($header)) {
26301f8b845SAndreas Gohr                if (is_a($column, 'dokuwiki\plugin\struct\meta\Column')) {
26407993756SAndreas Gohr                    $header = $column->getTranslatedLabel();
26507993756SAndreas Gohr                } else {
26607993756SAndreas Gohr                    $header = 'column ' . $num; // this should never happen
26707993756SAndreas Gohr                }
26807993756SAndreas Gohr            }
26907993756SAndreas Gohr
27007993756SAndreas Gohr            // simple mode first
27107993756SAndreas Gohr            if ($this->mode != 'xhtml') {
27207993756SAndreas Gohr                $this->renderer->tableheader_open();
27307993756SAndreas Gohr                $this->renderer->cdata($header);
27407993756SAndreas Gohr                $this->renderer->tableheader_close();
27507993756SAndreas Gohr                continue;
27607993756SAndreas Gohr            }
27707993756SAndreas Gohr
27807993756SAndreas Gohr            // still here? create custom header for more flexibility
27907993756SAndreas Gohr
2809113d04aSAndreas Gohr            // width setting, widths are prevalidated, no escape needed
28107993756SAndreas Gohr            $width = '';
2829113d04aSAndreas Gohr            if (isset($this->data['widths'][$num]) && $this->data['widths'][$num] != '-') {
2839113d04aSAndreas Gohr                $width = ' style="min-width: ' . $this->data['widths'][$num] . ';' .
2849113d04aSAndreas Gohr                    'max-width: ' . $this->data['widths'][$num] . ';"';
28507993756SAndreas Gohr            }
28607993756SAndreas Gohr
287d4b5a17cSAndreas Gohr            // prepare data attribute for inline edits
288d6d97f60SAnna Dabrowska            if (
289d6d97f60SAnna Dabrowska                !is_a($column, '\dokuwiki\plugin\struct\meta\PageColumn') &&
290d4b5a17cSAndreas Gohr                !is_a($column, '\dokuwiki\plugin\struct\meta\RevisionColumn')
291d4b5a17cSAndreas Gohr            ) {
292d4b5a17cSAndreas Gohr                $data = 'data-field="' . hsc($column->getFullQualifiedLabel()) . '"';
293d4b5a17cSAndreas Gohr            } else {
294d4b5a17cSAndreas Gohr                $data = '';
295d4b5a17cSAndreas Gohr            }
296d4b5a17cSAndreas Gohr
29707993756SAndreas Gohr            // sort indicator and link
29807993756SAndreas Gohr            $sortclass = '';
29907993756SAndreas Gohr            $sorts = $this->searchConfig->getSorts();
30007993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
301aa124708SAndreas Gohr            $dynamic->setSort($column, true);
30207993756SAndreas Gohr            if (isset($sorts[$column->getFullQualifiedLabel()])) {
303aa124708SAndreas Gohr                list(/*colname*/, $currentSort) = $sorts[$column->getFullQualifiedLabel()];
304aa124708SAndreas Gohr                if ($currentSort) {
30507993756SAndreas Gohr                    $sortclass = 'sort-down';
30607993756SAndreas Gohr                    $dynamic->setSort($column, false);
30707993756SAndreas Gohr                } else {
30807993756SAndreas Gohr                    $sortclass = 'sort-up';
30907993756SAndreas Gohr                }
31007993756SAndreas Gohr            }
31107993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
31207993756SAndreas Gohr
31307993756SAndreas Gohr            // output XHTML header
314d4b5a17cSAndreas Gohr            $this->renderer->doc .= "<th $width $data>";
315*17a3a578SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="' . $sortclass . '" ' .
316*17a3a578SAndreas Gohr                'title="' . $this->helper->getLang('sort') . '">' . hsc($header) . '</a>';
31707993756SAndreas Gohr            $this->renderer->doc .= '</th>';
31807993756SAndreas Gohr        }
31907993756SAndreas Gohr
32007993756SAndreas Gohr        $this->renderer->tablerow_close();
32107993756SAndreas Gohr    }
32207993756SAndreas Gohr
32307993756SAndreas Gohr    /**
324b7e1d73bSAndreas Gohr     * Is the result set currently dynamically filtered?
325b7e1d73bSAndreas Gohr     * @return bool
326b7e1d73bSAndreas Gohr     */
327d6d97f60SAnna Dabrowska    protected function isDynamicallyFiltered()
328d6d97f60SAnna Dabrowska    {
329b7e1d73bSAndreas Gohr        if ($this->mode != 'xhtml') return false;
330b7e1d73bSAndreas Gohr        if (!$this->data['dynfilters']) return false;
331b7e1d73bSAndreas Gohr
332b7e1d73bSAndreas Gohr        $dynamic = $this->searchConfig->getDynamicParameters();
333b7e1d73bSAndreas Gohr        return (bool)$dynamic->getFilters();
334b7e1d73bSAndreas Gohr    }
335b7e1d73bSAndreas Gohr
336b7e1d73bSAndreas Gohr    /**
33707993756SAndreas Gohr     * Add input fields for dynamic filtering
33807993756SAndreas Gohr     */
339d6d97f60SAnna Dabrowska    protected function renderDynamicFilters()
340d6d97f60SAnna Dabrowska    {
34107993756SAndreas Gohr        if ($this->mode != 'xhtml') return;
34207993756SAndreas Gohr        if (!$this->data['dynfilters']) return;
3431bc467a4SMichael Grosse        if (is_a($this->renderer, 'renderer_plugin_dw2pdf')) {
344e6ae02ecSMichael Grosse            return;
345e6ae02ecSMichael Grosse        }
3461bc467a4SMichael Grosse        global $conf;
34707993756SAndreas Gohr
34807993756SAndreas Gohr        $this->renderer->doc .= '<tr class="dataflt">';
34907993756SAndreas Gohr
35007993756SAndreas Gohr        // add extra column for row numbers
35107993756SAndreas Gohr        if ($this->data['rownumbers']) {
35207993756SAndreas Gohr            $this->renderer->doc .= '<th></th>';
35307993756SAndreas Gohr        }
35407993756SAndreas Gohr
35507993756SAndreas Gohr        // each column gets a form
35607993756SAndreas Gohr        foreach ($this->columns as $column) {
35707993756SAndreas Gohr            $this->renderer->doc .= '<th>';
358*17a3a578SAndreas Gohr
359*17a3a578SAndreas Gohr            // BEGIN FORM
36007993756SAndreas Gohr            $form = new \Doku_Form(array('method' => 'GET', 'action' => wl($this->id)));
36176195677SAndreas Gohr            unset($form->_hidden['sectok']); // we don't need it here
36276195677SAndreas Gohr            if (!$conf['userewrite']) $form->addHidden('id', $this->id);
36307993756SAndreas Gohr
36407993756SAndreas Gohr            // current value
36507993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
36607993756SAndreas Gohr            $filters = $dynamic->getFilters();
36707993756SAndreas Gohr            if (isset($filters[$column->getFullQualifiedLabel()])) {
36807993756SAndreas Gohr                list(, $current) = $filters[$column->getFullQualifiedLabel()];
36907993756SAndreas Gohr                $dynamic->removeFilter($column);
37007993756SAndreas Gohr            } else {
37107993756SAndreas Gohr                $current = '';
37207993756SAndreas Gohr            }
37307993756SAndreas Gohr
37407993756SAndreas Gohr            // Add current request params
37507993756SAndreas Gohr            $params = $dynamic->getURLParameters();
37607993756SAndreas Gohr            foreach ($params as $key => $val) {
37707993756SAndreas Gohr                $form->addHidden($key, $val);
37807993756SAndreas Gohr            }
37907993756SAndreas Gohr
38007993756SAndreas Gohr            // add input field
381db9b8745SAndreas Gohr            $key = $column->getFullQualifiedLabel() . $column->getType()->getDefaultComparator();
382*17a3a578SAndreas Gohr            $form->addElement(
383*17a3a578SAndreas Gohr                form_makeField('text', SearchConfigParameters::$PARAM_FILTER . '[' . $key . ']', $current, '')
384*17a3a578SAndreas Gohr            );
38507993756SAndreas Gohr            $this->renderer->doc .= $form->getForm();
386*17a3a578SAndreas Gohr            // END FORM
387*17a3a578SAndreas Gohr
38807993756SAndreas Gohr            $this->renderer->doc .= '</th>';
38907993756SAndreas Gohr        }
39007993756SAndreas Gohr        $this->renderer->doc .= '</tr>';
39107993756SAndreas Gohr    }
39207993756SAndreas Gohr
39307993756SAndreas Gohr    /**
39407993756SAndreas Gohr     * Display the actual table data
39507993756SAndreas Gohr     */
396d6d97f60SAnna Dabrowska    protected function renderResult()
397d6d97f60SAnna Dabrowska    {
39807993756SAndreas Gohr        foreach ($this->result as $rownum => $row) {
39947eb8cceSSzymon Olewniczak            $data = array(
40047eb8cceSSzymon Olewniczak                'id' => $this->id,
40147eb8cceSSzymon Olewniczak                'mode' => $this->mode,
40247eb8cceSSzymon Olewniczak                'renderer' => $this->renderer,
40347eb8cceSSzymon Olewniczak                'searchConfig' => $this->searchConfig,
40447eb8cceSSzymon Olewniczak                'data' => $this->data,
40547eb8cceSSzymon Olewniczak                'rownum' => &$rownum,
40647eb8cceSSzymon Olewniczak                'row' => &$row,
40747eb8cceSSzymon Olewniczak            );
40847eb8cceSSzymon Olewniczak            $evt = new \Doku_Event('PLUGIN_STRUCT_AGGREGATIONTABLE_RENDERRESULTROW', $data);
40947eb8cceSSzymon Olewniczak            if ($evt->advise_before()) {
410f107f479SAndreas Gohr                $this->renderResultRow($rownum, $row);
411f107f479SAndreas Gohr            }
41247eb8cceSSzymon Olewniczak            $evt->advise_after();
41347eb8cceSSzymon Olewniczak        }
414f107f479SAndreas Gohr    }
415f107f479SAndreas Gohr
416f107f479SAndreas Gohr    /**
417f107f479SAndreas Gohr     * Render a single result row
418f107f479SAndreas Gohr     *
419f107f479SAndreas Gohr     * @param int $rownum
420f107f479SAndreas Gohr     * @param array $row
421f107f479SAndreas Gohr     */
422d6d97f60SAnna Dabrowska    protected function renderResultRow($rownum, $row)
423d6d97f60SAnna Dabrowska    {
42407993756SAndreas Gohr        $this->renderer->tablerow_open();
42507993756SAndreas Gohr
426d4b5a17cSAndreas Gohr        // add data attribute for inline edit
427d4b5a17cSAndreas Gohr        if ($this->mode == 'xhtml') {
428d4b5a17cSAndreas Gohr            $pid = $this->resultPIDs[$rownum];
4290ceefd5cSAnna Dabrowska            $rid = $this->resultRids[$rownum];
4306fd73b4bSAnna Dabrowska            $rev = $this->resultRevs[$rownum];
431d4b5a17cSAndreas Gohr            $this->renderer->doc = substr(rtrim($this->renderer->doc), 0, -1); // remove closing '>'
4326fd73b4bSAnna Dabrowska            $this->renderer->doc .= ' data-pid="' . hsc($pid) . '" data-rev="' . $rev . '" data-rid="' . $rid . '">';
433d4b5a17cSAndreas Gohr        }
434d4b5a17cSAndreas Gohr
43507993756SAndreas Gohr        // row number column
43634ea6e10SAnna Dabrowska        if (!empty($this->data['rownumbers'])) {
43707993756SAndreas Gohr            $this->renderer->tablecell_open();
4383215aebfSSzymon Olewniczak            $searchConfigConf = $this->searchConfig->getConf();
4393215aebfSSzymon Olewniczak            $this->renderer->cdata($rownum + $searchConfigConf['offset'] + 1);
44007993756SAndreas Gohr            $this->renderer->tablecell_close();
44107993756SAndreas Gohr        }
44207993756SAndreas Gohr
44307993756SAndreas Gohr        /** @var Value $value */
44407993756SAndreas Gohr        foreach ($row as $colnum => $value) {
44534ea6e10SAnna Dabrowska            $align = isset($this->data['align'][$colnum]) ? $this->data['align'][$colnum] : null;
44634ea6e10SAnna Dabrowska            $this->renderer->tablecell_open(1, $align);
44707993756SAndreas Gohr            $value->render($this->renderer, $this->mode);
44807993756SAndreas Gohr            $this->renderer->tablecell_close();
44907993756SAndreas Gohr
45007993756SAndreas Gohr            // summarize
45107993756SAndreas Gohr            if ($this->data['summarize'] && is_numeric($value->getValue())) {
45207993756SAndreas Gohr                if (!isset($this->sums[$colnum])) {
45307993756SAndreas Gohr                    $this->sums[$colnum] = 0;
45407993756SAndreas Gohr                }
45507993756SAndreas Gohr                $this->sums[$colnum] += $value->getValue();
45607993756SAndreas Gohr            }
45707993756SAndreas Gohr        }
45807993756SAndreas Gohr        $this->renderer->tablerow_close();
45907993756SAndreas Gohr    }
46007993756SAndreas Gohr
46107993756SAndreas Gohr    /**
46207993756SAndreas Gohr     * Renders an information row for when no results were found
46307993756SAndreas Gohr     */
464d6d97f60SAnna Dabrowska    protected function renderEmptyResult()
465d6d97f60SAnna Dabrowska    {
46607993756SAndreas Gohr        $this->renderer->tablerow_open();
46770cf6339SAndreas Gohr        $this->renderer->tablecell_open(count($this->columns) + $this->data['rownumbers'], 'center');
46807993756SAndreas Gohr        $this->renderer->cdata($this->helper->getLang('none'));
46907993756SAndreas Gohr        $this->renderer->tablecell_close();
47007993756SAndreas Gohr        $this->renderer->tablerow_close();
47107993756SAndreas Gohr    }
47207993756SAndreas Gohr
47307993756SAndreas Gohr    /**
47407993756SAndreas Gohr     * Add sums if wanted
47507993756SAndreas Gohr     */
476d6d97f60SAnna Dabrowska    protected function renderSums()
477d6d97f60SAnna Dabrowska    {
478d18090e8SAndreas Gohr        if (empty($this->data['summarize'])) return;
47907993756SAndreas Gohr
480a0bf8bb2SAndreas Gohr        $this->renderer->info['struct_table_meta'] = true;
4818925ba29SAndreas Gohr        if ($this->mode == 'xhtml') {
4828925ba29SAndreas Gohr            /** @noinspection PhpMethodParametersCountMismatchInspection */
4838925ba29SAndreas Gohr            $this->renderer->tablerow_open('summarize');
4848925ba29SAndreas Gohr        } else {
48507993756SAndreas Gohr            $this->renderer->tablerow_open();
4868925ba29SAndreas Gohr        }
48707993756SAndreas Gohr
48807993756SAndreas Gohr        if ($this->data['rownumbers']) {
4898925ba29SAndreas Gohr            $this->renderer->tableheader_open();
4908925ba29SAndreas Gohr            $this->renderer->tableheader_close();
49107993756SAndreas Gohr        }
49207993756SAndreas Gohr
493aee4116bSAndreas Gohr        $len = count($this->columns);
49407993756SAndreas Gohr        for ($i = 0; $i < $len; $i++) {
4958925ba29SAndreas Gohr            $this->renderer->tableheader_open(1, $this->data['align'][$i]);
496aee4116bSAndreas Gohr            if (!empty($this->sums[$i])) {
4979b97e610SAndreas Gohr                $this->renderer->cdata('∑ ');
4989b97e610SAndreas Gohr                $this->columns[$i]->getType()->renderValue($this->sums[$i], $this->renderer, $this->mode);
49907993756SAndreas Gohr            } else {
50007993756SAndreas Gohr                if ($this->mode == 'xhtml') {
50107993756SAndreas Gohr                    $this->renderer->doc .= '&nbsp;';
50207993756SAndreas Gohr                }
50307993756SAndreas Gohr            }
5048925ba29SAndreas Gohr            $this->renderer->tableheader_close();
50507993756SAndreas Gohr        }
50607993756SAndreas Gohr        $this->renderer->tablerow_close();
507a0bf8bb2SAndreas Gohr        $this->renderer->info['struct_table_meta'] = false;
50807993756SAndreas Gohr    }
50907993756SAndreas Gohr
51007993756SAndreas Gohr    /**
511986ab7e6SAndreas Gohr     * Adds paging controls to the table
51207993756SAndreas Gohr     */
513d6d97f60SAnna Dabrowska    protected function renderPagingControls()
514d6d97f60SAnna Dabrowska    {
51507993756SAndreas Gohr        if (empty($this->data['limit'])) return;
516a0bf8bb2SAndreas Gohr        if ($this->mode != 'xhtml') return;
51707993756SAndreas Gohr
518a0bf8bb2SAndreas Gohr        $this->renderer->info['struct_table_meta'] = true;
51907993756SAndreas Gohr        $this->renderer->tablerow_open();
5204bc1074dSMichael Grosse        $this->renderer->tableheader_open((count($this->columns) + ($this->data['rownumbers'] ? 1 : 0)));
52107993756SAndreas Gohr        $offset = $this->data['offset'];
52207993756SAndreas Gohr
52307993756SAndreas Gohr        // prev link
52407993756SAndreas Gohr        if ($offset) {
52507993756SAndreas Gohr            $prev = $offset - $this->data['limit'];
52607993756SAndreas Gohr            if ($prev < 0) {
52707993756SAndreas Gohr                $prev = 0;
52807993756SAndreas Gohr            }
52907993756SAndreas Gohr
53007993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
53107993756SAndreas Gohr            $dynamic->setOffset($prev);
53207993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
53307993756SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="prev">' . $this->helper->getLang('prev') . '</a>';
53407993756SAndreas Gohr        }
53507993756SAndreas Gohr
53607993756SAndreas Gohr        // next link
53707993756SAndreas Gohr        if ($this->resultCount > $offset + $this->data['limit']) {
53807993756SAndreas Gohr            $next = $offset + $this->data['limit'];
53907993756SAndreas Gohr            $dynamic = $this->searchConfig->getDynamicParameters();
54007993756SAndreas Gohr            $dynamic->setOffset($next);
54107993756SAndreas Gohr            $link = wl($this->id, $dynamic->getURLParameters());
54207993756SAndreas Gohr            $this->renderer->doc .= '<a href="' . $link . '" class="next">' . $this->helper->getLang('next') . '</a>';
54307993756SAndreas Gohr        }
54407993756SAndreas Gohr
54507993756SAndreas Gohr        $this->renderer->tableheader_close();
54607993756SAndreas Gohr        $this->renderer->tablerow_close();
547a0bf8bb2SAndreas Gohr        $this->renderer->info['struct_table_meta'] = true;
54807993756SAndreas Gohr    }
54909dd691aSAndreas Gohr
55009dd691aSAndreas Gohr    /**
55109dd691aSAndreas Gohr     * Adds CSV export controls
55209dd691aSAndreas Gohr     */
553d6d97f60SAnna Dabrowska    protected function renderExportControls()
554d6d97f60SAnna Dabrowska    {
55509dd691aSAndreas Gohr        if ($this->mode != 'xhtml') return;
5567b240ca8SAndreas Gohr        if (empty($this->data['csv'])) return;
55709dd691aSAndreas Gohr        if (!$this->resultCount) return;
55809dd691aSAndreas Gohr
559c8ccdaf8SAndreas Gohr        $dynamic = $this->searchConfig->getDynamicParameters();
560c8ccdaf8SAndreas Gohr        $params = $dynamic->getURLParameters();
561c8ccdaf8SAndreas Gohr        $params['hash'] = $this->renderer->info['struct_table_hash'];
562c8ccdaf8SAndreas Gohr
56309dd691aSAndreas Gohr        // FIXME apply dynamic filters
564eafc109fSAndreas Gohr        $link = exportlink($this->id, 'struct_csv', $params);
56509dd691aSAndreas Gohr
566*17a3a578SAndreas Gohr        $this->renderer->doc .= '<a href="' . $link . '" class="export mediafile mf_csv">' .
567*17a3a578SAndreas Gohr            $this->helper->getLang('csvexport') . '</a>';
56809dd691aSAndreas Gohr    }
56907993756SAndreas Gohr}
570