xref: /plugin/struct/meta/NestedResult.php (revision 026d10982b153911adca3c4e1a49c30632725ac1)
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
1145bc00e11SAndreas Gohr        if ($valObj->getColumn()->isMulti()) {
1155bc00e11SAndreas Gohr            // split up multi values into separate nodes
1165bc00e11SAndreas Gohr            $values = $valObj->getValue();
1175bc00e11SAndreas Gohr            foreach ($values as $value) {
1185bc00e11SAndreas Gohr                $newValue = new Value($valObj->getColumn(), $value);
1195bc00e11SAndreas Gohr                $node = $this->getNodeForValue($newValue, $depth);
1205bc00e11SAndreas Gohr                $parent->addChild($node);
1215bc00e11SAndreas Gohr                $this->nestBranch($node, $row, $nesting, $depth + 1);
1225bc00e11SAndreas Gohr            }
1235bc00e11SAndreas Gohr        } else {
1245bc00e11SAndreas Gohr            $node = $this->getNodeForValue($valObj, $depth);
1255bc00e11SAndreas Gohr            $parent->addChild($node);
1265bc00e11SAndreas Gohr            $this->nestBranch($node, $row, $nesting, $depth + 1);
1275bc00e11SAndreas Gohr        }
1285bc00e11SAndreas Gohr    }
1295bc00e11SAndreas Gohr
1305bc00e11SAndreas Gohr    /**
1315bc00e11SAndreas Gohr     * Create or get existing Node from the tree
1325bc00e11SAndreas Gohr     *
1335bc00e11SAndreas Gohr     * @param Value $value
1345bc00e11SAndreas Gohr     * @param int $depth
1355bc00e11SAndreas Gohr     * @return NestedValue
1365bc00e11SAndreas Gohr     */
1375bc00e11SAndreas Gohr    protected function getNodeForValue(Value $value, $depth)
1385bc00e11SAndreas Gohr    {
1395bc00e11SAndreas Gohr        $node = new NestedValue($value, $depth);
1405bc00e11SAndreas Gohr        $key = (string)$node;
1415bc00e11SAndreas Gohr        if (!isset($this->nodes[$key])) {
1425bc00e11SAndreas Gohr            $this->nodes[$key] = $node;
1435bc00e11SAndreas Gohr        }
1445bc00e11SAndreas Gohr        return $this->nodes[$key];
1455bc00e11SAndreas Gohr    }
146ce44c639SAndreas Gohr
147ce44c639SAndreas Gohr    /**
148ce44c639SAndreas Gohr     * Create or get an existing Node for indexing
149ce44c639SAndreas Gohr     *
150ce44c639SAndreas Gohr     * @param Value $value
151ce44c639SAndreas Gohr     * @param int $index
152ce44c639SAndreas Gohr     * @return NestedValue
153ce44c639SAndreas Gohr     */
154ce44c639SAndreas Gohr    protected function getIndexNode(Value $value, $index)
155ce44c639SAndreas Gohr    {
156*026d1098SAndreas Gohr        $compare = $value->getDisplayValue();
157ce44c639SAndreas Gohr        if (is_array($compare)) $compare = $compare[0];
158ce44c639SAndreas Gohr        $key = PhpString::strtoupper(PhpString::substr($compare, 0, $index));
159ce44c639SAndreas Gohr
160ce44c639SAndreas Gohr        if (!isset($this->indexNodes[$key])) {
161ce44c639SAndreas Gohr            $col = new Column(
162ce44c639SAndreas Gohr                0,
163ce44c639SAndreas Gohr                new Text([], '%index%', false),
164ce44c639SAndreas Gohr                -1,
165ce44c639SAndreas Gohr                true,
166ce44c639SAndreas Gohr                $value->getColumn()->getTable()
167ce44c639SAndreas Gohr            );
168ce44c639SAndreas Gohr            $this->indexNodes[$key] = new NestedValue(new Value($col, $key), 0);
169ce44c639SAndreas Gohr        }
170ce44c639SAndreas Gohr
171ce44c639SAndreas Gohr        return $this->indexNodes[$key];
172ce44c639SAndreas Gohr    }
1735bc00e11SAndreas Gohr}
174