xref: /plugin/struct/meta/NestedResult.php (revision ae522a2d9d694eb7481a4511e8ef0301beb6db73)
15bc00e11SAndreas Gohr<?php
25bc00e11SAndreas Gohr
35bc00e11SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
45bc00e11SAndreas Gohr
5ce44c639SAndreas Gohruse dokuwiki\plugin\struct\types\Text;
6ce44c639SAndreas Gohruse dokuwiki\Utf8\PhpString;
7ce44c639SAndreas Gohr
85bc00e11SAndreas Gohr/**
95bc00e11SAndreas Gohr * This class builds a nested tree from a search result
105bc00e11SAndreas Gohr *
115bc00e11SAndreas Gohr * This is used to create the nested output in the AggregationList
125bc00e11SAndreas Gohr */
135bc00e11SAndreas Gohrclass NestedResult
145bc00e11SAndreas Gohr{
155bc00e11SAndreas Gohr    /** @var NestedValue[] */
165bc00e11SAndreas Gohr    protected $nodes = [];
175bc00e11SAndreas Gohr
18ce44c639SAndreas Gohr    /** @var NestedValue[] */
19ce44c639SAndreas Gohr    protected $indexNodes = [];
20ce44c639SAndreas Gohr
215bc00e11SAndreas Gohr    /** @var Value[][] */
225bc00e11SAndreas Gohr    protected $result;
235bc00e11SAndreas Gohr
245bc00e11SAndreas Gohr    /**
255bc00e11SAndreas Gohr     * @param Value[][] $result the original search result
265bc00e11SAndreas Gohr     * @return void
275bc00e11SAndreas Gohr     */
285bc00e11SAndreas Gohr    public function __construct($result)
295bc00e11SAndreas Gohr    {
305bc00e11SAndreas Gohr        $this->result = $result;
315bc00e11SAndreas Gohr    }
325bc00e11SAndreas Gohr
335bc00e11SAndreas Gohr    /**
345bc00e11SAndreas Gohr     * Get the nested result
355bc00e11SAndreas Gohr     *
365bc00e11SAndreas Gohr     * @param int $nesting the nesting level to use
37ce44c639SAndreas Gohr     * @param int $index the number of characters to use for indexing
385bc00e11SAndreas Gohr     * @return NestedValue the root node of the nested tree
395bc00e11SAndreas Gohr     */
40ce44c639SAndreas Gohr    public function getRoot($nesting, $index = 0)
41ce44c639SAndreas Gohr    {
425bc00e11SAndreas Gohr        $this->nodes = [];
435bc00e11SAndreas Gohr        $root = new NestedValue(null, -1);
445bc00e11SAndreas Gohr
455bc00e11SAndreas Gohr        if (!$this->result) return $root;
46ce44c639SAndreas Gohr
475bc00e11SAndreas Gohr        foreach ($this->result as $row) {
485bc00e11SAndreas Gohr            $this->nestBranch($root, $row, $nesting);
495bc00e11SAndreas Gohr        }
505bc00e11SAndreas Gohr
51ce44c639SAndreas Gohr        $root = $this->createIndex($root, $index);
525bc00e11SAndreas Gohr        return $root;
535bc00e11SAndreas Gohr    }
545bc00e11SAndreas Gohr
555bc00e11SAndreas Gohr    /**
56ce44c639SAndreas Gohr     * Add a top level index to the tree
57ce44c639SAndreas Gohr     *
58ce44c639SAndreas Gohr     * @param NestedValue $root Root node of the tree
59ce44c639SAndreas Gohr     * @param int $index Number of characters to use for indexing
60ce44c639SAndreas Gohr     * @return NestedValue new root node
61ce44c639SAndreas Gohr     */
62ce44c639SAndreas Gohr    protected function createIndex(NestedValue $root, $index)
63ce44c639SAndreas Gohr    {
64ce44c639SAndreas Gohr        if (!$index) return $root;
65ce44c639SAndreas Gohr        $this->indexNodes = [];
66ce44c639SAndreas Gohr
67ce44c639SAndreas Gohr        $children = $root->getChildren();
68ce44c639SAndreas Gohr        $resultRows = $root->getResultRows();
69ce44c639SAndreas Gohr        if ($children) {
70ce44c639SAndreas Gohr            // there are children, so we are a nested result
71ce44c639SAndreas Gohr            foreach ($children as $child) {
72ce44c639SAndreas Gohr                $indexValue = $child->getValueObject();
73ce44c639SAndreas Gohr                $indexNode = $this->getIndexNode($indexValue, $index);
74ce44c639SAndreas Gohr                $indexNode->addChild($child);
75ce44c639SAndreas Gohr                $child->setDepth(1); // increase child's depth from 0 to 1
76ce44c639SAndreas Gohr            }
77ce44c639SAndreas Gohr        } elseif ($resultRows) {
78ce44c639SAndreas Gohr            // no children, so we are currently a flat result
79ce44c639SAndreas Gohr            foreach ($resultRows as $row) {
80ce44c639SAndreas Gohr                $indexValue = $row[0];
81ce44c639SAndreas Gohr                $indexNode = $this->getIndexNode($indexValue, $index);
82ce44c639SAndreas Gohr                $indexNode->addResultRow($row);
83ce44c639SAndreas Gohr            }
84ce44c639SAndreas Gohr        }
85ce44c639SAndreas Gohr
86ce44c639SAndreas Gohr        // now all results are added to index nodes - use them as children
87ce44c639SAndreas Gohr        $newRoot = new NestedValue(null, -1);
88ce44c639SAndreas Gohr        foreach ($this->indexNodes as $node) {
89ce44c639SAndreas Gohr            $newRoot->addChild($node);
90ce44c639SAndreas Gohr        }
91ce44c639SAndreas Gohr        return $newRoot;
92ce44c639SAndreas Gohr    }
93ce44c639SAndreas Gohr
94ce44c639SAndreas Gohr    /**
955bc00e11SAndreas Gohr     * Creates nested nodes for a given result row
965bc00e11SAndreas Gohr     *
975bc00e11SAndreas Gohr     * Splits up multi Values into separate nodes, when used in nesting
985bc00e11SAndreas Gohr     *
995bc00e11SAndreas Gohr     * @param Value[] $row current result row to work on
1005bc00e11SAndreas Gohr     * @param int $nesting number of wanted nesting levels
1015bc00e11SAndreas Gohr     * @param int $depth current nesting depth (used in recursion)
1025bc00e11SAndreas Gohr     */
1035bc00e11SAndreas Gohr    protected function nestBranch(NestedValue $parent, $row, $nesting, $depth = 0)
1045bc00e11SAndreas Gohr    {
1055bc00e11SAndreas Gohr        // nesting level reached, add row and return
1065bc00e11SAndreas Gohr        if ($depth >= $nesting) {
1075bc00e11SAndreas Gohr            $parent->addResultRow($row);
1085bc00e11SAndreas Gohr            return;
1095bc00e11SAndreas Gohr        }
1105bc00e11SAndreas Gohr
1115bc00e11SAndreas Gohr        $valObj = array_shift($row);
1125bc00e11SAndreas Gohr        if (!$valObj) return; // no more values to nest, usually shouldn't happen
1135bc00e11SAndreas Gohr
114*ae522a2dSAndreas Gohr        $parentPath = (string) $parent;
115*ae522a2dSAndreas Gohr
1167b7a9290SAndreas Gohr        if ($valObj->getColumn()->isMulti() && $valObj->getValue()) {
1175bc00e11SAndreas Gohr            // split up multi values into separate nodes
1185bc00e11SAndreas Gohr            $values = $valObj->getValue();
1197b7a9290SAndreas Gohr            if ($values) {
1205bc00e11SAndreas Gohr                foreach ($values as $value) {
1215bc00e11SAndreas Gohr                    $newValue = new Value($valObj->getColumn(), $value);
122*ae522a2dSAndreas Gohr                    $node = $this->getNodeForValue($newValue, $parentPath, $depth);
1235bc00e11SAndreas Gohr                    $parent->addChild($node);
1245bc00e11SAndreas Gohr                    $this->nestBranch($node, $row, $nesting, $depth + 1);
1255bc00e11SAndreas Gohr                }
1265bc00e11SAndreas Gohr            } else {
1277b7a9290SAndreas Gohr                $newValue = new Value($valObj->getColumn(), ''); // add empty node
128*ae522a2dSAndreas Gohr                $node = $this->getNodeForValue($newValue, $parentPath, $depth);
1297b7a9290SAndreas Gohr                $parent->addChild($node);
1307b7a9290SAndreas Gohr                $this->nestBranch($node, $row, $nesting, $depth + 1);
1317b7a9290SAndreas Gohr            }
1327b7a9290SAndreas Gohr        } else {
133*ae522a2dSAndreas Gohr            $node = $this->getNodeForValue($valObj, $parentPath, $depth);
1345bc00e11SAndreas Gohr            $parent->addChild($node);
1355bc00e11SAndreas Gohr            $this->nestBranch($node, $row, $nesting, $depth + 1);
1365bc00e11SAndreas Gohr        }
1375bc00e11SAndreas Gohr    }
1385bc00e11SAndreas Gohr
1395bc00e11SAndreas Gohr    /**
1405bc00e11SAndreas Gohr     * Create or get existing Node from the tree
1415bc00e11SAndreas Gohr     *
1425bc00e11SAndreas Gohr     * @param Value $value
1435bc00e11SAndreas Gohr     * @param int $depth
1445bc00e11SAndreas Gohr     * @return NestedValue
1455bc00e11SAndreas Gohr     */
146*ae522a2dSAndreas Gohr    protected function getNodeForValue(Value $value, $parentPath, $depth)
1475bc00e11SAndreas Gohr    {
148*ae522a2dSAndreas Gohr        $node = new NestedValue($value, $parentPath, $depth);
1495bc00e11SAndreas Gohr        $key = (string)$node;
1505bc00e11SAndreas Gohr        if (!isset($this->nodes[$key])) {
1515bc00e11SAndreas Gohr            $this->nodes[$key] = $node;
1525bc00e11SAndreas Gohr        }
1535bc00e11SAndreas Gohr        return $this->nodes[$key];
1545bc00e11SAndreas Gohr    }
155ce44c639SAndreas Gohr
156ce44c639SAndreas Gohr    /**
157ce44c639SAndreas Gohr     * Create or get an existing Node for indexing
158ce44c639SAndreas Gohr     *
159ce44c639SAndreas Gohr     * @param Value $value
160ce44c639SAndreas Gohr     * @param int $index
161ce44c639SAndreas Gohr     * @return NestedValue
162ce44c639SAndreas Gohr     */
163ce44c639SAndreas Gohr    protected function getIndexNode(Value $value, $index)
164ce44c639SAndreas Gohr    {
165026d1098SAndreas Gohr        $compare = $value->getDisplayValue();
166ce44c639SAndreas Gohr        if (is_array($compare)) $compare = $compare[0];
167ce44c639SAndreas Gohr        $key = PhpString::strtoupper(PhpString::substr($compare, 0, $index));
168ce44c639SAndreas Gohr
169ce44c639SAndreas Gohr        if (!isset($this->indexNodes[$key])) {
170ce44c639SAndreas Gohr            $col = new Column(
171ce44c639SAndreas Gohr                0,
172ce44c639SAndreas Gohr                new Text([], '%index%', false),
173ce44c639SAndreas Gohr                -1,
174ce44c639SAndreas Gohr                true,
175ce44c639SAndreas Gohr                $value->getColumn()->getTable()
176ce44c639SAndreas Gohr            );
177ce44c639SAndreas Gohr            $this->indexNodes[$key] = new NestedValue(new Value($col, $key), 0);
178ce44c639SAndreas Gohr        }
179ce44c639SAndreas Gohr
180ce44c639SAndreas Gohr        return $this->indexNodes[$key];
181ce44c639SAndreas Gohr    }
1825bc00e11SAndreas Gohr}
183