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; 25*ae522a2dSAndreas Gohr /** 26*ae522a2dSAndreas Gohr * @var mixed|string 27*ae522a2dSAndreas Gohr */ 28*ae522a2dSAndreas 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 */ 36*ae522a2dSAndreas Gohr public function __construct(?Value $value, $parentPath = '', $depth = 0) 375bc00e11SAndreas Gohr { 385bc00e11SAndreas Gohr $this->value = $value; 39*ae522a2dSAndreas 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 1171ee0a2dcSAndreas Gohr $ident = md5(array_reduce($row, function ($carry, $value) { 1181ee0a2dcSAndreas Gohr return $carry . $value; 1191ee0a2dcSAndreas Gohr }, '')); 1201ee0a2dcSAndreas Gohr 1211ee0a2dcSAndreas Gohr $this->resultRows[$ident] = $row; 1225bc00e11SAndreas Gohr } 1235bc00e11SAndreas Gohr 1245bc00e11SAndreas Gohr /** 1255bc00e11SAndreas Gohr * Get all result rows stored in this node 1265bc00e11SAndreas Gohr * 1275bc00e11SAndreas Gohr * @return Value[][] 1285bc00e11SAndreas Gohr */ 1295bc00e11SAndreas Gohr public function getResultRows() 1305bc00e11SAndreas Gohr { 1311ee0a2dcSAndreas Gohr return array_values($this->resultRows); 1325bc00e11SAndreas Gohr } 1335bc00e11SAndreas Gohr 1345bc00e11SAndreas Gohr /** 1355bc00e11SAndreas Gohr * Get a unique key for this node 1365bc00e11SAndreas Gohr * 1375bc00e11SAndreas Gohr * @return string 1385bc00e11SAndreas Gohr */ 1395bc00e11SAndreas Gohr public function __toString() 1405bc00e11SAndreas Gohr { 1415bc00e11SAndreas Gohr if ($this->value === null) return ''; // root node 142*ae522a2dSAndreas Gohr return $this->parentPath . '/' . $this->value->__toString(); 1435bc00e11SAndreas Gohr } 1445bc00e11SAndreas Gohr 1455bc00e11SAndreas Gohr /** 1465bc00e11SAndreas Gohr * Custom comparator to sort the children of this node 1475bc00e11SAndreas Gohr * 1485bc00e11SAndreas Gohr * @param NestedValue $a 1495bc00e11SAndreas Gohr * @param NestedValue $b 1505bc00e11SAndreas Gohr * @return int 1515bc00e11SAndreas Gohr */ 1525bc00e11SAndreas Gohr public function sortChildren(NestedValue $a, NestedValue $b) 1535bc00e11SAndreas Gohr { 1547b7a9290SAndreas Gohr $compA = join('-', (array)$a->getValueObject()->getCompareValue()); 1557b7a9290SAndreas Gohr $compB = join('-', (array)$b->getValueObject()->getCompareValue()); 1567b7a9290SAndreas Gohr 1577b7a9290SAndreas Gohr // sort empty values to the end 1587b7a9290SAndreas Gohr if ($compA === $compB) { 1597b7a9290SAndreas Gohr return 0; 1607b7a9290SAndreas Gohr } 1617b7a9290SAndreas Gohr if ($compA === '') { 1627b7a9290SAndreas Gohr return 1; 1637b7a9290SAndreas Gohr } 1647b7a9290SAndreas Gohr if ($compB === '') { 1657b7a9290SAndreas Gohr return -1; 1667b7a9290SAndreas Gohr } 1677b7a9290SAndreas Gohr 1685bc00e11SAndreas Gohr // note: the way NestedResults build the NestedValues, the value object should 1695bc00e11SAndreas Gohr // always contain a single value only. But since the associated column is still 1705bc00e11SAndreas Gohr // a multi-value column, getCompareValue() will still return an array. 1715bc00e11SAndreas Gohr // So here we treat all returns as array and join them with a dash (even though 1725bc00e11SAndreas Gohr // there should never be more than one value in there) 1737b7a9290SAndreas Gohr return Sort::strcmp($compA, $compB); 1745bc00e11SAndreas Gohr } 1755bc00e11SAndreas Gohr 1761ee0a2dcSAndreas Gohr /** 1771ee0a2dcSAndreas Gohr * print the tree for debugging 1781ee0a2dcSAndreas Gohr * 179*ae522a2dSAndreas Gohr * @param bool $sort use sorted children? 1801ee0a2dcSAndreas Gohr * @return string 1811ee0a2dcSAndreas Gohr */ 182*ae522a2dSAndreas Gohr public function dump($sort = true) 1831ee0a2dcSAndreas Gohr { 1841ee0a2dcSAndreas Gohr $return = ''; 1851ee0a2dcSAndreas Gohr 1861ee0a2dcSAndreas Gohr if ($this->value) { 1871855a1f9SAndreas Gohr $val = join(', ', (array)$this->value->getDisplayValue()); 1881855a1f9SAndreas Gohr if ($val === '') $val = '{n/a}'; 1891ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1901855a1f9SAndreas Gohr $return .= $val; 1911ee0a2dcSAndreas Gohr $return .= "\n"; 1921ee0a2dcSAndreas Gohr } else { 1931ee0a2dcSAndreas Gohr $return .= "*\n"; 1941ee0a2dcSAndreas Gohr } 1951ee0a2dcSAndreas Gohr 1961ee0a2dcSAndreas Gohr foreach ($this->getResultRows() as $row) { 1971ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1981ee0a2dcSAndreas Gohr foreach ($row as $value) { 1991855a1f9SAndreas Gohr $val = join(', ', (array)$value->getDisplayValue()); 2001855a1f9SAndreas Gohr if ($val === '') $val = '{n/a}'; 2011855a1f9SAndreas Gohr $return .= ' ' . $val; 2021ee0a2dcSAndreas Gohr } 2031ee0a2dcSAndreas Gohr $return .= "\n"; 2041ee0a2dcSAndreas Gohr } 2051ee0a2dcSAndreas Gohr 206*ae522a2dSAndreas Gohr foreach ($this->getChildren($sort) as $child) { 2071ee0a2dcSAndreas Gohr $return .= $child->dump(); 2081ee0a2dcSAndreas Gohr } 2091ee0a2dcSAndreas Gohr 2101ee0a2dcSAndreas Gohr return $return; 2111ee0a2dcSAndreas Gohr } 2125bc00e11SAndreas Gohr} 213