xref: /plugin/struct/meta/NestedValue.php (revision 5e29103a15bd9873f422f66a6a5239b6aec4651e)
15bc00e11SAndreas Gohr<?php
25bc00e11SAndreas Gohr
35bc00e11SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
45bc00e11SAndreas Gohr
55bc00e11SAndreas Gohruse dokuwiki\Utf8\Sort;
65bc00e11SAndreas Gohr
75bc00e11SAndreas Gohr/**
85bc00e11SAndreas Gohr * Object to create a tree of values
95bc00e11SAndreas Gohr *
105bc00e11SAndreas Gohr * You should not create these yourself, but use the NestedResult class instead
115bc00e11SAndreas Gohr */
125bc00e11SAndreas Gohrclass NestedValue
135bc00e11SAndreas Gohr{
145bc00e11SAndreas Gohr    /** @var Value */
155bc00e11SAndreas Gohr    protected $value;
165bc00e11SAndreas Gohr
175bc00e11SAndreas Gohr    /** @var NestedValue[] */
185bc00e11SAndreas Gohr    protected $children = [];
195bc00e11SAndreas Gohr
205bc00e11SAndreas Gohr    /** @var Value[][] */
215bc00e11SAndreas Gohr    protected $resultRows = [];
225bc00e11SAndreas Gohr
235bc00e11SAndreas Gohr    /** @var int the nesting depth */
245bc00e11SAndreas Gohr    protected $depth;
25ae522a2dSAndreas Gohr    /**
26ae522a2dSAndreas Gohr     * @var mixed|string
27ae522a2dSAndreas Gohr     */
28ae522a2dSAndreas Gohr    protected $parentPath;
295bc00e11SAndreas Gohr
305bc00e11SAndreas Gohr    /**
315bc00e11SAndreas Gohr     * Create a nested version of the given value
325bc00e11SAndreas Gohr     *
335bc00e11SAndreas Gohr     * @param Value|null $value The value to store, null for root node
345bc00e11SAndreas Gohr     * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels)
355bc00e11SAndreas Gohr     */
36ae522a2dSAndreas Gohr    public function __construct(?Value $value, $parentPath = '', $depth = 0)
375bc00e11SAndreas Gohr    {
385bc00e11SAndreas Gohr        $this->value = $value;
39ae522a2dSAndreas Gohr        $this->parentPath = $parentPath;
405bc00e11SAndreas Gohr        $this->depth = $depth;
415bc00e11SAndreas Gohr    }
425bc00e11SAndreas Gohr
435bc00e11SAndreas Gohr    /**
445bc00e11SAndreas Gohr     * @return int
455bc00e11SAndreas Gohr     */
465bc00e11SAndreas Gohr    public function getDepth()
475bc00e11SAndreas Gohr    {
485bc00e11SAndreas Gohr        return $this->depth;
495bc00e11SAndreas Gohr    }
505bc00e11SAndreas Gohr
515bc00e11SAndreas Gohr    /**
52ce44c639SAndreas Gohr     * @param int $depth
53ce44c639SAndreas Gohr     */
54ce44c639SAndreas Gohr    public function setDepth($depth)
55ce44c639SAndreas Gohr    {
56ce44c639SAndreas Gohr        $this->depth = $depth;
57ce44c639SAndreas Gohr        foreach ($this->children as $child) {
58ce44c639SAndreas Gohr            $child->setDepth($depth + 1);
59ce44c639SAndreas Gohr        }
60ce44c639SAndreas Gohr    }
61ce44c639SAndreas Gohr
62ce44c639SAndreas Gohr    /**
635bc00e11SAndreas Gohr     * Access the stored value
645bc00e11SAndreas Gohr     *
655bc00e11SAndreas Gohr     * @return Value|null the value stored in this node, null for root node
665bc00e11SAndreas Gohr     */
675bc00e11SAndreas Gohr    public function getValueObject()
685bc00e11SAndreas Gohr    {
695bc00e11SAndreas Gohr        return $this->value;
705bc00e11SAndreas Gohr    }
715bc00e11SAndreas Gohr
725bc00e11SAndreas Gohr    /**
735bc00e11SAndreas Gohr     * Add a child node
745bc00e11SAndreas Gohr     *
755bc00e11SAndreas Gohr     * Nodes with the same key (__toString()) will be overwritten
765bc00e11SAndreas Gohr     *
775bc00e11SAndreas Gohr     * @param NestedValue $child
785bc00e11SAndreas Gohr     * @return void
795bc00e11SAndreas Gohr     */
805bc00e11SAndreas Gohr    public function addChild(NestedValue $child)
815bc00e11SAndreas Gohr    {
825bc00e11SAndreas Gohr        $this->children[(string)$child] = $child; // ensures uniqueness
835bc00e11SAndreas Gohr    }
845bc00e11SAndreas Gohr
855bc00e11SAndreas Gohr    /**
865bc00e11SAndreas Gohr     * Get all child nodes
875bc00e11SAndreas Gohr     *
881f53b3d5SAndreas Gohr     * @param bool $sort should children be sorted alphabetically?
895bc00e11SAndreas Gohr     * @return NestedValue[]
905bc00e11SAndreas Gohr     */
911f53b3d5SAndreas Gohr    public function getChildren($sort = false)
925bc00e11SAndreas Gohr    {
935bc00e11SAndreas Gohr        $children = $this->children;
941f53b3d5SAndreas Gohr
951f53b3d5SAndreas Gohr        if ($sort) {
965bc00e11SAndreas Gohr            usort($children, [$this, 'sortChildren']);
971f53b3d5SAndreas Gohr        } elseif (isset($children[''])) {
981f53b3d5SAndreas Gohr            // even when not sorting, make sure the n/a entries are last
991f53b3d5SAndreas Gohr            $naKids = $children[''];
1001f53b3d5SAndreas Gohr            unset($children['']);
1011f53b3d5SAndreas Gohr            $children[''] = $naKids;
1021f53b3d5SAndreas Gohr        }
1031f53b3d5SAndreas Gohr        return array_values($children);
1045bc00e11SAndreas Gohr    }
1055bc00e11SAndreas Gohr
1065bc00e11SAndreas Gohr    /**
1075bc00e11SAndreas Gohr     * Add a result row to this node
1085bc00e11SAndreas Gohr     *
1091ee0a2dcSAndreas Gohr     * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result
1101ee0a2dcSAndreas Gohr     *
1115bc00e11SAndreas Gohr     * @param Value[] $row
1125bc00e11SAndreas Gohr     * @return void
1135bc00e11SAndreas Gohr     */
1145bc00e11SAndreas Gohr    public function addResultRow($row)
1155bc00e11SAndreas Gohr    {
1161ee0a2dcSAndreas Gohr        // only add unique rows
117*5e29103aSannda        $ident = md5(array_reduce($row, static fn($carry, $value) => $carry . $value, ''));
1181ee0a2dcSAndreas Gohr
1191ee0a2dcSAndreas Gohr        $this->resultRows[$ident] = $row;
1205bc00e11SAndreas Gohr    }
1215bc00e11SAndreas Gohr
1225bc00e11SAndreas Gohr    /**
1235bc00e11SAndreas Gohr     * Get all result rows stored in this node
1245bc00e11SAndreas Gohr     *
1255bc00e11SAndreas Gohr     * @return Value[][]
1265bc00e11SAndreas Gohr     */
1275bc00e11SAndreas Gohr    public function getResultRows()
1285bc00e11SAndreas Gohr    {
1291ee0a2dcSAndreas Gohr        return array_values($this->resultRows);
1305bc00e11SAndreas Gohr    }
1315bc00e11SAndreas Gohr
1325bc00e11SAndreas Gohr    /**
1335bc00e11SAndreas Gohr     * Get a unique key for this node
1345bc00e11SAndreas Gohr     *
1355bc00e11SAndreas Gohr     * @return string
1365bc00e11SAndreas Gohr     */
1375bc00e11SAndreas Gohr    public function __toString()
1385bc00e11SAndreas Gohr    {
1397234bfb1Ssplitbrain        if (!$this->value instanceof Value) return ''; // root node
140ae522a2dSAndreas Gohr        return $this->parentPath . '/' . $this->value->__toString();
1415bc00e11SAndreas Gohr    }
1425bc00e11SAndreas Gohr
1435bc00e11SAndreas Gohr    /**
1445bc00e11SAndreas Gohr     * Custom comparator to sort the children of this node
1455bc00e11SAndreas Gohr     *
1465bc00e11SAndreas Gohr     * @param NestedValue $a
1475bc00e11SAndreas Gohr     * @param NestedValue $b
1485bc00e11SAndreas Gohr     * @return int
1495bc00e11SAndreas Gohr     */
1505bc00e11SAndreas Gohr    public function sortChildren(NestedValue $a, NestedValue $b)
1515bc00e11SAndreas Gohr    {
1527234bfb1Ssplitbrain        $compA = implode('-', (array)$a->getValueObject()->getCompareValue());
1537234bfb1Ssplitbrain        $compB = implode('-', (array)$b->getValueObject()->getCompareValue());
1547b7a9290SAndreas Gohr
1557b7a9290SAndreas Gohr        // sort empty values to the end
1567b7a9290SAndreas Gohr        if ($compA === $compB) {
1577b7a9290SAndreas Gohr            return 0;
1587b7a9290SAndreas Gohr        }
1597b7a9290SAndreas Gohr        if ($compA === '') {
1607b7a9290SAndreas Gohr            return 1;
1617b7a9290SAndreas Gohr        }
1627b7a9290SAndreas Gohr        if ($compB === '') {
1637b7a9290SAndreas Gohr            return -1;
1647b7a9290SAndreas Gohr        }
1657b7a9290SAndreas Gohr
1665bc00e11SAndreas Gohr        // note: the way NestedResults build the NestedValues, the value object should
1675bc00e11SAndreas Gohr        // always contain a single value only. But since the associated column is still
1685bc00e11SAndreas Gohr        // a multi-value column, getCompareValue() will still return an array.
1695bc00e11SAndreas Gohr        // So here we treat all returns as array and join them with a dash (even though
1705bc00e11SAndreas Gohr        // there should never be more than one value in there)
1717b7a9290SAndreas Gohr        return Sort::strcmp($compA, $compB);
1725bc00e11SAndreas Gohr    }
1735bc00e11SAndreas Gohr
1741ee0a2dcSAndreas Gohr    /**
1751ee0a2dcSAndreas Gohr     * print the tree for debugging
1761ee0a2dcSAndreas Gohr     *
177ae522a2dSAndreas Gohr     * @param bool $sort use sorted children?
1781ee0a2dcSAndreas Gohr     * @return string
1791ee0a2dcSAndreas Gohr     */
180ae522a2dSAndreas Gohr    public function dump($sort = true)
1811ee0a2dcSAndreas Gohr    {
1821ee0a2dcSAndreas Gohr        $return = '';
1831ee0a2dcSAndreas Gohr
1841ee0a2dcSAndreas Gohr        if ($this->value) {
1857234bfb1Ssplitbrain            $val = implode(', ', (array)$this->value->getDisplayValue());
1861855a1f9SAndreas Gohr            if ($val === '') $val = '{n/a}';
1871ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1881855a1f9SAndreas Gohr            $return .= $val;
1891ee0a2dcSAndreas Gohr            $return .= "\n";
1901ee0a2dcSAndreas Gohr        } else {
1911ee0a2dcSAndreas Gohr            $return .= "*\n";
1921ee0a2dcSAndreas Gohr        }
1931ee0a2dcSAndreas Gohr
1941ee0a2dcSAndreas Gohr        foreach ($this->getResultRows() as $row) {
1951ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1961ee0a2dcSAndreas Gohr            foreach ($row as $value) {
1977234bfb1Ssplitbrain                $val = implode(', ', (array)$value->getDisplayValue());
1981855a1f9SAndreas Gohr                if ($val === '') $val = '{n/a}';
1991855a1f9SAndreas Gohr                $return .= ' ' . $val;
2001ee0a2dcSAndreas Gohr            }
2011ee0a2dcSAndreas Gohr            $return .= "\n";
2021ee0a2dcSAndreas Gohr        }
2031ee0a2dcSAndreas Gohr
204ae522a2dSAndreas Gohr        foreach ($this->getChildren($sort) as $child) {
2051ee0a2dcSAndreas Gohr            $return .= $child->dump();
2061ee0a2dcSAndreas Gohr        }
2071ee0a2dcSAndreas Gohr
2081ee0a2dcSAndreas Gohr        return $return;
2091ee0a2dcSAndreas Gohr    }
2105bc00e11SAndreas Gohr}
211