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; 255bc00e11SAndreas Gohr 265bc00e11SAndreas Gohr /** 275bc00e11SAndreas Gohr * Create a nested version of the given value 285bc00e11SAndreas Gohr * 295bc00e11SAndreas Gohr * @param Value|null $value The value to store, null for root node 305bc00e11SAndreas Gohr * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels) 315bc00e11SAndreas Gohr */ 325bc00e11SAndreas Gohr public function __construct(?Value $value, $depth = 0) 335bc00e11SAndreas Gohr { 345bc00e11SAndreas Gohr $this->value = $value; 355bc00e11SAndreas Gohr $this->depth = $depth; 365bc00e11SAndreas Gohr } 375bc00e11SAndreas Gohr 385bc00e11SAndreas Gohr /** 395bc00e11SAndreas Gohr * @return int 405bc00e11SAndreas Gohr */ 415bc00e11SAndreas Gohr public function getDepth() 425bc00e11SAndreas Gohr { 435bc00e11SAndreas Gohr return $this->depth; 445bc00e11SAndreas Gohr } 455bc00e11SAndreas Gohr 465bc00e11SAndreas Gohr /** 47ce44c639SAndreas Gohr * @param int $depth 48ce44c639SAndreas Gohr */ 49ce44c639SAndreas Gohr public function setDepth($depth) 50ce44c639SAndreas Gohr { 51ce44c639SAndreas Gohr $this->depth = $depth; 52ce44c639SAndreas Gohr foreach ($this->children as $child) { 53ce44c639SAndreas Gohr $child->setDepth($depth + 1); 54ce44c639SAndreas Gohr } 55ce44c639SAndreas Gohr } 56ce44c639SAndreas Gohr 57ce44c639SAndreas Gohr /** 585bc00e11SAndreas Gohr * Access the stored value 595bc00e11SAndreas Gohr * 605bc00e11SAndreas Gohr * @return Value|null the value stored in this node, null for root node 615bc00e11SAndreas Gohr */ 625bc00e11SAndreas Gohr public function getValueObject() 635bc00e11SAndreas Gohr { 645bc00e11SAndreas Gohr return $this->value; 655bc00e11SAndreas Gohr } 665bc00e11SAndreas Gohr 675bc00e11SAndreas Gohr /** 685bc00e11SAndreas Gohr * Add a child node 695bc00e11SAndreas Gohr * 705bc00e11SAndreas Gohr * Nodes with the same key (__toString()) will be overwritten 715bc00e11SAndreas Gohr * 725bc00e11SAndreas Gohr * @param NestedValue $child 735bc00e11SAndreas Gohr * @return void 745bc00e11SAndreas Gohr */ 755bc00e11SAndreas Gohr public function addChild(NestedValue $child) 765bc00e11SAndreas Gohr { 775bc00e11SAndreas Gohr $this->children[(string)$child] = $child; // ensures uniqueness 785bc00e11SAndreas Gohr } 795bc00e11SAndreas Gohr 805bc00e11SAndreas Gohr /** 815bc00e11SAndreas Gohr * Get all child nodes 825bc00e11SAndreas Gohr * 83*1f53b3d5SAndreas Gohr * @param bool $sort should children be sorted alphabetically? 845bc00e11SAndreas Gohr * @return NestedValue[] 855bc00e11SAndreas Gohr */ 86*1f53b3d5SAndreas Gohr public function getChildren($sort = false) 875bc00e11SAndreas Gohr { 885bc00e11SAndreas Gohr $children = $this->children; 89*1f53b3d5SAndreas Gohr 90*1f53b3d5SAndreas Gohr if($sort) { 915bc00e11SAndreas Gohr usort($children, [$this, 'sortChildren']); 92*1f53b3d5SAndreas Gohr } elseif(isset($children[''])) { 93*1f53b3d5SAndreas Gohr // even when not sorting, make sure the n/a entries are last 94*1f53b3d5SAndreas Gohr $naKids = $children['']; 95*1f53b3d5SAndreas Gohr unset($children['']); 96*1f53b3d5SAndreas Gohr $children[''] = $naKids; 97*1f53b3d5SAndreas Gohr } 98*1f53b3d5SAndreas Gohr return array_values($children); 995bc00e11SAndreas Gohr } 1005bc00e11SAndreas Gohr 1015bc00e11SAndreas Gohr /** 1025bc00e11SAndreas Gohr * Add a result row to this node 1035bc00e11SAndreas Gohr * 1041ee0a2dcSAndreas Gohr * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result 1051ee0a2dcSAndreas Gohr * 1065bc00e11SAndreas Gohr * @param Value[] $row 1075bc00e11SAndreas Gohr * @return void 1085bc00e11SAndreas Gohr */ 1095bc00e11SAndreas Gohr public function addResultRow($row) 1105bc00e11SAndreas Gohr { 1111ee0a2dcSAndreas Gohr // only add unique rows 1121ee0a2dcSAndreas Gohr $ident = md5(array_reduce($row, function ($carry, $value) { 1131ee0a2dcSAndreas Gohr return $carry . $value; 1141ee0a2dcSAndreas Gohr }, '')); 1151ee0a2dcSAndreas Gohr 1161ee0a2dcSAndreas Gohr $this->resultRows[$ident] = $row; 1175bc00e11SAndreas Gohr } 1185bc00e11SAndreas Gohr 1195bc00e11SAndreas Gohr /** 1205bc00e11SAndreas Gohr * Get all result rows stored in this node 1215bc00e11SAndreas Gohr * 1225bc00e11SAndreas Gohr * @return Value[][] 1235bc00e11SAndreas Gohr */ 1245bc00e11SAndreas Gohr public function getResultRows() 1255bc00e11SAndreas Gohr { 1261ee0a2dcSAndreas Gohr return array_values($this->resultRows); 1275bc00e11SAndreas Gohr } 1285bc00e11SAndreas Gohr 1295bc00e11SAndreas Gohr /** 1305bc00e11SAndreas Gohr * Get a unique key for this node 1315bc00e11SAndreas Gohr * 1325bc00e11SAndreas Gohr * @return string 1335bc00e11SAndreas Gohr */ 1345bc00e11SAndreas Gohr public function __toString() 1355bc00e11SAndreas Gohr { 1365bc00e11SAndreas Gohr if ($this->value === null) return ''; // root node 1375bc00e11SAndreas Gohr return $this->value->__toString() . '-' . $this->depth; 1385bc00e11SAndreas Gohr } 1395bc00e11SAndreas Gohr 1405bc00e11SAndreas Gohr /** 1415bc00e11SAndreas Gohr * Custom comparator to sort the children of this node 1425bc00e11SAndreas Gohr * 1435bc00e11SAndreas Gohr * @param NestedValue $a 1445bc00e11SAndreas Gohr * @param NestedValue $b 1455bc00e11SAndreas Gohr * @return int 1465bc00e11SAndreas Gohr */ 1475bc00e11SAndreas Gohr public function sortChildren(NestedValue $a, NestedValue $b) 1485bc00e11SAndreas Gohr { 1497b7a9290SAndreas Gohr $compA = join('-', (array)$a->getValueObject()->getCompareValue()); 1507b7a9290SAndreas Gohr $compB = join('-', (array)$b->getValueObject()->getCompareValue()); 1517b7a9290SAndreas Gohr 1527b7a9290SAndreas Gohr // sort empty values to the end 1537b7a9290SAndreas Gohr if ($compA === $compB) { 1547b7a9290SAndreas Gohr return 0; 1557b7a9290SAndreas Gohr } 1567b7a9290SAndreas Gohr if ($compA === '') { 1577b7a9290SAndreas Gohr return 1; 1587b7a9290SAndreas Gohr } 1597b7a9290SAndreas Gohr if ($compB === '') { 1607b7a9290SAndreas Gohr return -1; 1617b7a9290SAndreas Gohr } 1627b7a9290SAndreas Gohr 1635bc00e11SAndreas Gohr // note: the way NestedResults build the NestedValues, the value object should 1645bc00e11SAndreas Gohr // always contain a single value only. But since the associated column is still 1655bc00e11SAndreas Gohr // a multi-value column, getCompareValue() will still return an array. 1665bc00e11SAndreas Gohr // So here we treat all returns as array and join them with a dash (even though 1675bc00e11SAndreas Gohr // there should never be more than one value in there) 1687b7a9290SAndreas Gohr return Sort::strcmp($compA, $compB); 1695bc00e11SAndreas Gohr } 1705bc00e11SAndreas Gohr 1711ee0a2dcSAndreas Gohr /** 1721ee0a2dcSAndreas Gohr * print the tree for debugging 1731ee0a2dcSAndreas Gohr * 1741ee0a2dcSAndreas Gohr * @return string 1751ee0a2dcSAndreas Gohr */ 1761ee0a2dcSAndreas Gohr public function dump() 1771ee0a2dcSAndreas Gohr { 1781ee0a2dcSAndreas Gohr $return = ''; 1791ee0a2dcSAndreas Gohr 1801ee0a2dcSAndreas Gohr if ($this->value) { 1811855a1f9SAndreas Gohr $val = join(', ', (array)$this->value->getDisplayValue()); 1821855a1f9SAndreas Gohr if ($val === '') $val = '{n/a}'; 1831ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1841855a1f9SAndreas Gohr $return .= $val; 1851ee0a2dcSAndreas Gohr $return .= "\n"; 1861ee0a2dcSAndreas Gohr } else { 1871ee0a2dcSAndreas Gohr $return .= "*\n"; 1881ee0a2dcSAndreas Gohr } 1891ee0a2dcSAndreas Gohr 1901ee0a2dcSAndreas Gohr foreach ($this->getResultRows() as $row) { 1911ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1921ee0a2dcSAndreas Gohr foreach ($row as $value) { 1931855a1f9SAndreas Gohr $val = join(', ', (array)$value->getDisplayValue()); 1941855a1f9SAndreas Gohr if ($val === '') $val = '{n/a}'; 1951855a1f9SAndreas Gohr $return .= ' ' . $val; 1961ee0a2dcSAndreas Gohr } 1971ee0a2dcSAndreas Gohr $return .= "\n"; 1981ee0a2dcSAndreas Gohr } 1991ee0a2dcSAndreas Gohr 2001ee0a2dcSAndreas Gohr foreach ($this->getChildren() as $child) { 2011ee0a2dcSAndreas Gohr $return .= $child->dump(); 2021ee0a2dcSAndreas Gohr } 2031ee0a2dcSAndreas Gohr 2041ee0a2dcSAndreas Gohr return $return; 2051ee0a2dcSAndreas Gohr } 2065bc00e11SAndreas Gohr} 207