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 155bc00e11SAndreas Gohr /** @var Value */ 165bc00e11SAndreas Gohr protected $value; 175bc00e11SAndreas Gohr 185bc00e11SAndreas Gohr /** @var NestedValue[] */ 195bc00e11SAndreas Gohr protected $children = []; 205bc00e11SAndreas Gohr 215bc00e11SAndreas Gohr /** @var Value[][] */ 225bc00e11SAndreas Gohr protected $resultRows = []; 235bc00e11SAndreas Gohr 245bc00e11SAndreas Gohr /** @var int the nesting depth */ 255bc00e11SAndreas Gohr protected $depth; 265bc00e11SAndreas Gohr 275bc00e11SAndreas Gohr /** 285bc00e11SAndreas Gohr * Create a nested version of the given value 295bc00e11SAndreas Gohr * 305bc00e11SAndreas Gohr * @param Value|null $value The value to store, null for root node 315bc00e11SAndreas Gohr * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels) 325bc00e11SAndreas Gohr */ 335bc00e11SAndreas Gohr public function __construct(?Value $value, $depth = 0) 345bc00e11SAndreas Gohr { 355bc00e11SAndreas Gohr $this->value = $value; 365bc00e11SAndreas Gohr $this->depth = $depth; 375bc00e11SAndreas Gohr } 385bc00e11SAndreas Gohr 395bc00e11SAndreas Gohr /** 405bc00e11SAndreas Gohr * @return int 415bc00e11SAndreas Gohr */ 425bc00e11SAndreas Gohr public function getDepth() 435bc00e11SAndreas Gohr { 445bc00e11SAndreas Gohr return $this->depth; 455bc00e11SAndreas Gohr } 465bc00e11SAndreas Gohr 475bc00e11SAndreas Gohr /** 48*ce44c639SAndreas Gohr * @param int $depth 49*ce44c639SAndreas Gohr */ 50*ce44c639SAndreas Gohr public function setDepth($depth) 51*ce44c639SAndreas Gohr { 52*ce44c639SAndreas Gohr $this->depth = $depth; 53*ce44c639SAndreas Gohr foreach ($this->children as $child) { 54*ce44c639SAndreas Gohr $child->setDepth($depth + 1); 55*ce44c639SAndreas Gohr } 56*ce44c639SAndreas Gohr } 57*ce44c639SAndreas Gohr 58*ce44c639SAndreas Gohr /** 595bc00e11SAndreas Gohr * Access the stored value 605bc00e11SAndreas Gohr * 615bc00e11SAndreas Gohr * @return Value|null the value stored in this node, null for root node 625bc00e11SAndreas Gohr */ 635bc00e11SAndreas Gohr public function getValueObject() 645bc00e11SAndreas Gohr { 655bc00e11SAndreas Gohr return $this->value; 665bc00e11SAndreas Gohr } 675bc00e11SAndreas Gohr 685bc00e11SAndreas Gohr /** 695bc00e11SAndreas Gohr * Add a child node 705bc00e11SAndreas Gohr * 715bc00e11SAndreas Gohr * Nodes with the same key (__toString()) will be overwritten 725bc00e11SAndreas Gohr * 735bc00e11SAndreas Gohr * @param NestedValue $child 745bc00e11SAndreas Gohr * @return void 755bc00e11SAndreas Gohr */ 765bc00e11SAndreas Gohr public function addChild(NestedValue $child) 775bc00e11SAndreas Gohr { 785bc00e11SAndreas Gohr $this->children[(string)$child] = $child; // ensures uniqueness 795bc00e11SAndreas Gohr } 805bc00e11SAndreas Gohr 815bc00e11SAndreas Gohr /** 825bc00e11SAndreas Gohr * Get all child nodes 835bc00e11SAndreas Gohr * 845bc00e11SAndreas Gohr * @return NestedValue[] 855bc00e11SAndreas Gohr */ 865bc00e11SAndreas Gohr public function getChildren() 875bc00e11SAndreas Gohr { 885bc00e11SAndreas Gohr $children = $this->children; 895bc00e11SAndreas Gohr usort($children, [$this, 'sortChildren']); 905bc00e11SAndreas Gohr return $children; 915bc00e11SAndreas Gohr } 925bc00e11SAndreas Gohr 935bc00e11SAndreas Gohr /** 945bc00e11SAndreas Gohr * Add a result row to this node 955bc00e11SAndreas Gohr * 961ee0a2dcSAndreas Gohr * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result 971ee0a2dcSAndreas Gohr * 985bc00e11SAndreas Gohr * @param Value[] $row 995bc00e11SAndreas Gohr * @return void 1005bc00e11SAndreas Gohr */ 1015bc00e11SAndreas Gohr public function addResultRow($row) 1025bc00e11SAndreas Gohr { 1031ee0a2dcSAndreas Gohr // only add unique rows 1041ee0a2dcSAndreas Gohr $ident = md5(array_reduce($row, function ($carry, $value) { 1051ee0a2dcSAndreas Gohr return $carry . $value; 1061ee0a2dcSAndreas Gohr }, '')); 1071ee0a2dcSAndreas Gohr 1081ee0a2dcSAndreas Gohr $this->resultRows[$ident] = $row; 1095bc00e11SAndreas Gohr } 1105bc00e11SAndreas Gohr 1115bc00e11SAndreas Gohr /** 1125bc00e11SAndreas Gohr * Get all result rows stored in this node 1135bc00e11SAndreas Gohr * 1145bc00e11SAndreas Gohr * @return Value[][] 1155bc00e11SAndreas Gohr */ 1165bc00e11SAndreas Gohr public function getResultRows() 1175bc00e11SAndreas Gohr { 1181ee0a2dcSAndreas Gohr return array_values($this->resultRows); 1195bc00e11SAndreas Gohr } 1205bc00e11SAndreas Gohr 1215bc00e11SAndreas Gohr /** 1225bc00e11SAndreas Gohr * Get a unique key for this node 1235bc00e11SAndreas Gohr * 1245bc00e11SAndreas Gohr * @return string 1255bc00e11SAndreas Gohr */ 1265bc00e11SAndreas Gohr public function __toString() 1275bc00e11SAndreas Gohr { 1285bc00e11SAndreas Gohr if ($this->value === null) return ''; // root node 1295bc00e11SAndreas Gohr return $this->value->__toString() . '-' . $this->depth; 1305bc00e11SAndreas Gohr } 1315bc00e11SAndreas Gohr 1325bc00e11SAndreas Gohr /** 1335bc00e11SAndreas Gohr * Custom comparator to sort the children of this node 1345bc00e11SAndreas Gohr * 1355bc00e11SAndreas Gohr * @param NestedValue $a 1365bc00e11SAndreas Gohr * @param NestedValue $b 1375bc00e11SAndreas Gohr * @return int 1385bc00e11SAndreas Gohr */ 1395bc00e11SAndreas Gohr public function sortChildren(NestedValue $a, NestedValue $b) 1405bc00e11SAndreas Gohr { 1415bc00e11SAndreas Gohr // note: the way NestedResults build the NestedValues, the value object should 1425bc00e11SAndreas Gohr // always contain a single value only. But since the associated column is still 1435bc00e11SAndreas Gohr // a multi-value column, getCompareValue() will still return an array. 1445bc00e11SAndreas Gohr // So here we treat all returns as array and join them with a dash (even though 1455bc00e11SAndreas Gohr // there should never be more than one value in there) 1465bc00e11SAndreas Gohr return Sort::strcmp( 1475bc00e11SAndreas Gohr join('-', (array)$a->getValueObject()->getCompareValue()), 1485bc00e11SAndreas Gohr join('-', (array)$b->getValueObject()->getCompareValue()) 1495bc00e11SAndreas Gohr ); 1505bc00e11SAndreas Gohr } 1515bc00e11SAndreas Gohr 1521ee0a2dcSAndreas Gohr /** 1531ee0a2dcSAndreas Gohr * print the tree for debugging 1541ee0a2dcSAndreas Gohr * 1551ee0a2dcSAndreas Gohr * @return string 1561ee0a2dcSAndreas Gohr */ 1571ee0a2dcSAndreas Gohr public function dump() 1581ee0a2dcSAndreas Gohr { 1591ee0a2dcSAndreas Gohr $return = ''; 1601ee0a2dcSAndreas Gohr 1611ee0a2dcSAndreas Gohr if ($this->value) { 1621ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1631ee0a2dcSAndreas Gohr $return .= join(', ', (array)$this->value->getDisplayValue()); 1641ee0a2dcSAndreas Gohr $return .= "\n"; 1651ee0a2dcSAndreas Gohr } else { 1661ee0a2dcSAndreas Gohr $return .= "*\n"; 1671ee0a2dcSAndreas Gohr } 1681ee0a2dcSAndreas Gohr 1691ee0a2dcSAndreas Gohr foreach ($this->getResultRows() as $row) { 1701ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 1711ee0a2dcSAndreas Gohr foreach ($row as $value) { 1721ee0a2dcSAndreas Gohr $return .= ' ' . join(', ', (array)$value->getDisplayValue()); 1731ee0a2dcSAndreas Gohr } 1741ee0a2dcSAndreas Gohr $return .= "\n"; 1751ee0a2dcSAndreas Gohr } 1761ee0a2dcSAndreas Gohr 1771ee0a2dcSAndreas Gohr foreach ($this->getChildren() as $child) { 1781ee0a2dcSAndreas Gohr $return .= $child->dump(); 1791ee0a2dcSAndreas Gohr } 1801ee0a2dcSAndreas Gohr 1811ee0a2dcSAndreas Gohr return $return; 1821ee0a2dcSAndreas Gohr } 1835bc00e11SAndreas Gohr} 184