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