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 * 835bc00e11SAndreas Gohr * @return NestedValue[] 845bc00e11SAndreas Gohr */ 855bc00e11SAndreas Gohr public function getChildren() 865bc00e11SAndreas Gohr { 875bc00e11SAndreas Gohr $children = $this->children; 885bc00e11SAndreas Gohr usort($children, [$this, 'sortChildren']); 895bc00e11SAndreas Gohr return $children; 905bc00e11SAndreas Gohr } 915bc00e11SAndreas Gohr 925bc00e11SAndreas Gohr /** 935bc00e11SAndreas Gohr * Add a result row to this node 945bc00e11SAndreas Gohr * 951ee0a2dcSAndreas Gohr * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result 961ee0a2dcSAndreas Gohr * 975bc00e11SAndreas Gohr * @param Value[] $row 985bc00e11SAndreas Gohr * @return void 995bc00e11SAndreas Gohr */ 1005bc00e11SAndreas Gohr public function addResultRow($row) 1015bc00e11SAndreas Gohr { 1021ee0a2dcSAndreas Gohr // only add unique rows 1031ee0a2dcSAndreas Gohr $ident = md5(array_reduce($row, function ($carry, $value) { 1041ee0a2dcSAndreas Gohr return $carry . $value; 1051ee0a2dcSAndreas Gohr }, '')); 1061ee0a2dcSAndreas Gohr 1071ee0a2dcSAndreas Gohr $this->resultRows[$ident] = $row; 1085bc00e11SAndreas Gohr } 1095bc00e11SAndreas Gohr 1105bc00e11SAndreas Gohr /** 1115bc00e11SAndreas Gohr * Get all result rows stored in this node 1125bc00e11SAndreas Gohr * 1135bc00e11SAndreas Gohr * @return Value[][] 1145bc00e11SAndreas Gohr */ 1155bc00e11SAndreas Gohr public function getResultRows() 1165bc00e11SAndreas Gohr { 1171ee0a2dcSAndreas Gohr return array_values($this->resultRows); 1185bc00e11SAndreas Gohr } 1195bc00e11SAndreas Gohr 1205bc00e11SAndreas Gohr /** 1215bc00e11SAndreas Gohr * Get a unique key for this node 1225bc00e11SAndreas Gohr * 1235bc00e11SAndreas Gohr * @return string 1245bc00e11SAndreas Gohr */ 1255bc00e11SAndreas Gohr public function __toString() 1265bc00e11SAndreas Gohr { 1275bc00e11SAndreas Gohr if ($this->value === null) return ''; // root node 1285bc00e11SAndreas Gohr return $this->value->__toString() . '-' . $this->depth; 1295bc00e11SAndreas Gohr } 1305bc00e11SAndreas Gohr 1315bc00e11SAndreas Gohr /** 1325bc00e11SAndreas Gohr * Custom comparator to sort the children of this node 1335bc00e11SAndreas Gohr * 1345bc00e11SAndreas Gohr * @param NestedValue $a 1355bc00e11SAndreas Gohr * @param NestedValue $b 1365bc00e11SAndreas Gohr * @return int 1375bc00e11SAndreas Gohr */ 1385bc00e11SAndreas Gohr public function sortChildren(NestedValue $a, NestedValue $b) 1395bc00e11SAndreas Gohr { 140*7b7a9290SAndreas Gohr $compA = join('-', (array)$a->getValueObject()->getCompareValue()); 141*7b7a9290SAndreas Gohr $compB = join('-', (array)$b->getValueObject()->getCompareValue()); 142*7b7a9290SAndreas Gohr 143*7b7a9290SAndreas Gohr // sort empty values to the end 144*7b7a9290SAndreas Gohr if($compA === $compB) { 145*7b7a9290SAndreas Gohr return 0; 146*7b7a9290SAndreas Gohr } 147*7b7a9290SAndreas Gohr if($compA === '') { 148*7b7a9290SAndreas Gohr return 1; 149*7b7a9290SAndreas Gohr } 150*7b7a9290SAndreas Gohr if($compB === '') { 151*7b7a9290SAndreas Gohr return -1; 152*7b7a9290SAndreas Gohr } 153*7b7a9290SAndreas Gohr 1545bc00e11SAndreas Gohr // note: the way NestedResults build the NestedValues, the value object should 1555bc00e11SAndreas Gohr // always contain a single value only. But since the associated column is still 1565bc00e11SAndreas Gohr // a multi-value column, getCompareValue() will still return an array. 1575bc00e11SAndreas Gohr // So here we treat all returns as array and join them with a dash (even though 1585bc00e11SAndreas Gohr // there should never be more than one value in there) 159*7b7a9290SAndreas Gohr return Sort::strcmp($compA, $compB); 1605bc00e11SAndreas Gohr } 1615bc00e11SAndreas Gohr 1621ee0a2dcSAndreas Gohr /** 1631ee0a2dcSAndreas Gohr * print the tree for debugging 1641ee0a2dcSAndreas Gohr * 1651ee0a2dcSAndreas Gohr * @return string 1661ee0a2dcSAndreas Gohr */ 1671ee0a2dcSAndreas Gohr public function dump() 1681ee0a2dcSAndreas Gohr { 1691ee0a2dcSAndreas Gohr $return = ''; 1701ee0a2dcSAndreas Gohr 1711ee0a2dcSAndreas Gohr if ($this->value) { 1721ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1731ee0a2dcSAndreas Gohr $return .= join(', ', (array)$this->value->getDisplayValue()); 1741ee0a2dcSAndreas Gohr $return .= "\n"; 1751ee0a2dcSAndreas Gohr } else { 1761ee0a2dcSAndreas Gohr $return .= "*\n"; 1771ee0a2dcSAndreas Gohr } 1781ee0a2dcSAndreas Gohr 1791ee0a2dcSAndreas Gohr foreach ($this->getResultRows() as $row) { 1801ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1811ee0a2dcSAndreas Gohr foreach ($row as $value) { 1821ee0a2dcSAndreas Gohr $return .= ' ' . join(', ', (array)$value->getDisplayValue()); 1831ee0a2dcSAndreas Gohr } 1841ee0a2dcSAndreas Gohr $return .= "\n"; 1851ee0a2dcSAndreas Gohr } 1861ee0a2dcSAndreas Gohr 1871ee0a2dcSAndreas Gohr foreach ($this->getChildren() as $child) { 1881ee0a2dcSAndreas Gohr $return .= $child->dump(); 1891ee0a2dcSAndreas Gohr } 1901ee0a2dcSAndreas Gohr 1911ee0a2dcSAndreas Gohr return $return; 1921ee0a2dcSAndreas Gohr } 1935bc00e11SAndreas Gohr} 194