xref: /plugin/struct/meta/NestedResult.php (revision 7b7a9290f8a0cb5d24bd768166706412896cffe5)
1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4
5use dokuwiki\plugin\struct\types\Text;
6use dokuwiki\Utf8\PhpString;
7
8/**
9 * This class builds a nested tree from a search result
10 *
11 * This is used to create the nested output in the AggregationList
12 */
13class NestedResult
14{
15    /** @var NestedValue[] */
16    protected $nodes = [];
17
18    /** @var NestedValue[] */
19    protected $indexNodes = [];
20
21    /** @var Value[][] */
22    protected $result;
23
24    /**
25     * @param Value[][] $result the original search result
26     * @return void
27     */
28    public function __construct($result)
29    {
30        $this->result = $result;
31    }
32
33    /**
34     * Get the nested result
35     *
36     * @param int $nesting the nesting level to use
37     * @param int $index the number of characters to use for indexing
38     * @return NestedValue the root node of the nested tree
39     */
40    public function getRoot($nesting, $index = 0)
41    {
42        $this->nodes = [];
43        $root = new NestedValue(null, -1);
44
45        if (!$this->result) return $root;
46
47        foreach ($this->result as $row) {
48            $this->nestBranch($root, $row, $nesting);
49        }
50
51        $root = $this->createIndex($root, $index);
52        return $root;
53    }
54
55    /**
56     * Add a top level index to the tree
57     *
58     * @param NestedValue $root Root node of the tree
59     * @param int $index Number of characters to use for indexing
60     * @return NestedValue new root node
61     */
62    protected function createIndex(NestedValue $root, $index)
63    {
64        if (!$index) return $root;
65        $this->indexNodes = [];
66
67        $children = $root->getChildren();
68        $resultRows = $root->getResultRows();
69        if ($children) {
70            // there are children, so we are a nested result
71            foreach ($children as $child) {
72                $indexValue = $child->getValueObject();
73                $indexNode = $this->getIndexNode($indexValue, $index);
74                $indexNode->addChild($child);
75                $child->setDepth(1); // increase child's depth from 0 to 1
76            }
77        } elseif ($resultRows) {
78            // no children, so we are currently a flat result
79            foreach ($resultRows as $row) {
80                $indexValue = $row[0];
81                $indexNode = $this->getIndexNode($indexValue, $index);
82                $indexNode->addResultRow($row);
83            }
84        }
85
86        // now all results are added to index nodes - use them as children
87        $newRoot = new NestedValue(null, -1);
88        foreach ($this->indexNodes as $node) {
89            $newRoot->addChild($node);
90        }
91        return $newRoot;
92    }
93
94    /**
95     * Creates nested nodes for a given result row
96     *
97     * Splits up multi Values into separate nodes, when used in nesting
98     *
99     * @param Value[] $row current result row to work on
100     * @param int $nesting number of wanted nesting levels
101     * @param int $depth current nesting depth (used in recursion)
102     */
103    protected function nestBranch(NestedValue $parent, $row, $nesting, $depth = 0)
104    {
105        // nesting level reached, add row and return
106        if ($depth >= $nesting) {
107            $parent->addResultRow($row);
108            return;
109        }
110
111        $valObj = array_shift($row);
112        if (!$valObj) return; // no more values to nest, usually shouldn't happen
113
114        if ($valObj->getColumn()->isMulti() && $valObj->getValue()) {
115            // split up multi values into separate nodes
116            $values = $valObj->getValue();
117            if($values) {
118                foreach ($values as $value) {
119                    $newValue = new Value($valObj->getColumn(), $value);
120                    $node = $this->getNodeForValue($newValue, $depth);
121                    $parent->addChild($node);
122                    $this->nestBranch($node, $row, $nesting, $depth + 1);
123                }
124            } else {
125                $newValue = new Value($valObj->getColumn(), ''); // add empty node
126                $node = $this->getNodeForValue($newValue, $depth);
127                $parent->addChild($node);
128                $this->nestBranch($node, $row, $nesting, $depth + 1);
129            }
130        } else {
131            $node = $this->getNodeForValue($valObj, $depth);
132            $parent->addChild($node);
133            $this->nestBranch($node, $row, $nesting, $depth + 1);
134        }
135    }
136
137    /**
138     * Create or get existing Node from the tree
139     *
140     * @param Value $value
141     * @param int $depth
142     * @return NestedValue
143     */
144    protected function getNodeForValue(Value $value, $depth)
145    {
146        $node = new NestedValue($value, $depth);
147        $key = (string)$node;
148        if (!isset($this->nodes[$key])) {
149            $this->nodes[$key] = $node;
150        }
151        return $this->nodes[$key];
152    }
153
154    /**
155     * Create or get an existing Node for indexing
156     *
157     * @param Value $value
158     * @param int $index
159     * @return NestedValue
160     */
161    protected function getIndexNode(Value $value, $index)
162    {
163        $compare = $value->getDisplayValue();
164        if (is_array($compare)) $compare = $compare[0];
165        $key = PhpString::strtoupper(PhpString::substr($compare, 0, $index));
166
167        if (!isset($this->indexNodes[$key])) {
168            $col = new Column(
169                0,
170                new Text([], '%index%', false),
171                -1,
172                true,
173                $value->getColumn()->getTable()
174            );
175            $this->indexNodes[$key] = new NestedValue(new Value($col, $key), 0);
176        }
177
178        return $this->indexNodes[$key];
179    }
180}
181