1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4
5/**
6 * Class AggregationList
7 *
8 * @package dokuwiki\plugin\struct\meta
9 */
10class AggregationList extends Aggregation
11{
12    /** @var int number of all results */
13    protected $resultColumnCount;
14
15    /** @inheritdoc */
16    public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig)
17    {
18        parent::__construct($id, $mode, $renderer, $searchConfig);
19        $this->resultColumnCount = count($this->columns);
20    }
21
22    /** @inheritdoc */
23    public function render($showNotFound = false)
24    {
25        if ($this->searchConfig->getResult()) {
26            $nestedResult = new NestedResult($this->searchConfig->getRows());
27            $root = $nestedResult->getRoot($this->data['nesting'], $this->data['index']);
28            $this->renderNode($root);
29        } elseif ($showNotFound) {
30            $this->renderer->cdata($this->helper->getLang('none'));
31        }
32    }
33
34    /**
35     * Recursively render the result tree
36     *
37     * @param NestedValue $node
38     * @return void
39     */
40    protected function renderNode(NestedValue $node)
41    {
42        $self = $node->getValueObject(); // null for root node
43        $children = $node->getChildren(!$self instanceof Value && $this->data['index']); // sort only for index
44        $results = $node->getResultRows();
45
46        // all our content is in a listitem, unless we are the root node
47        if ($self instanceof Value) {
48            $this->renderer->listitem_open($node->getDepth() + 1); // levels are 1 based
49        }
50
51        // render own value if available
52        if ($self instanceof Value) {
53            $this->renderer->listcontent_open();
54            $this->renderListItem([$self], $node->getDepth(), true); // zero based depth
55            $this->renderer->listcontent_close();
56        }
57
58        // render children or results as sub-list
59        if ($children || $results) {
60            $this->renderer->listu_open();
61
62            foreach ($children as $child) {
63                $this->renderNode($child);
64            }
65
66            foreach ($results as $result) {
67                $this->renderer->listitem_open($node->getDepth() + 2); // levels are 1 based, this is one deeper
68                $this->renderer->listcontent_open();
69                $this->renderListItem($result, $node->getDepth() + 1); // zero based depth, one deeper
70                $this->renderer->listcontent_close();
71                $this->renderer->listitem_close();
72            }
73
74            $this->renderer->listu_close();
75        }
76
77        // close listitem if opened
78        if ($self instanceof Value) {
79            $this->renderer->listitem_close();
80        }
81    }
82
83    /**
84     * Render the content of a single list item
85     *
86     * @param Value[] $resultrow
87     * @param int $depth The current nesting depth (zero based)
88     * @param bool $showEmpty show a placeholder for empty values?
89     */
90    protected function renderListItem($resultrow, $depth, $showEmpty = false)
91    {
92        $config = $this->searchConfig->getConf();
93        $sepbyheaders = $config['sepbyheaders'];
94        $headers = $config['headers'];
95
96        foreach ($resultrow as $index => $value) {
97            // when nesting, the resultrow is shifted by the nesting depth
98            $column = $index;
99            if ($config['nesting']) {
100                $column += $depth;
101            }
102            if ($sepbyheaders && !empty($headers[$column])) {
103                $header = $headers[$column];
104            } else {
105                $header = '';
106            }
107
108            if ($this->mode === 'xhtml') {
109                $this->renderValueXHTML($value, $header, $showEmpty);
110            } else {
111                $this->renderValueGeneric($value, $header, $showEmpty);
112            }
113        }
114    }
115
116    /**
117     * Render the given Value in a XHTML renderer
118     * @param Value $value
119     * @param string $header
120     * @param bool $showEmpty
121     * @return void
122     */
123    protected function renderValueXHTML($value, $header, $showEmpty = false)
124    {
125        $attributes = [
126            'data-struct-column' => strtolower($value->getColumn()->getFullQualifiedLabel()),
127            'data-struct-type' => strtolower($value->getColumn()->getType()->getClass()),
128            'class' => 'li', // default dokuwiki content wrapper
129        ];
130
131        $this->renderer->doc .= sprintf('<div %s>', buildAttributes($attributes)); // wrapper
132        if ($header !== '') {
133            $this->renderer->doc .= sprintf('<span class="struct_header">%s</span> ', hsc($header));
134        }
135        $this->renderer->doc .= '<div class="struct_value">';
136        if ($value->isEmpty() && $showEmpty) {
137            $this->renderer->doc .= '<span class="struct_na">' . $this->helper->getLang('na') . '</span>';
138        } else {
139            $value->render($this->renderer, $this->mode);
140        }
141        $this->renderer->doc .= '</div>';
142        $this->renderer->doc .= '</div> '; // wrapper
143    }
144
145    /**
146     * Render the given Value in any non-XHTML renderer
147     * @param Value $value
148     * @param string $header
149     * @return void
150     */
151    protected function renderValueGeneric($value, $header, $showEmpty = false)
152    {
153        $this->renderer->listcontent_open();
154        if ($header !== '') $this->renderer->cdata($header . ' ');
155        if ($value->isEmpty() && $showEmpty) {
156            $this->renderer->cdata($this->helper->getLang('na'));
157        } else {
158            $value->render($this->renderer, $this->mode);
159        }
160        $this->renderer->listcontent_close();
161    }
162}
163