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 /** 485bc00e11SAndreas Gohr * Access the stored value 495bc00e11SAndreas Gohr * 505bc00e11SAndreas Gohr * @return Value|null the value stored in this node, null for root node 515bc00e11SAndreas Gohr */ 525bc00e11SAndreas Gohr public function getValueObject() 535bc00e11SAndreas Gohr { 545bc00e11SAndreas Gohr return $this->value; 555bc00e11SAndreas Gohr } 565bc00e11SAndreas Gohr 575bc00e11SAndreas Gohr /** 585bc00e11SAndreas Gohr * Add a child node 595bc00e11SAndreas Gohr * 605bc00e11SAndreas Gohr * Nodes with the same key (__toString()) will be overwritten 615bc00e11SAndreas Gohr * 625bc00e11SAndreas Gohr * @param NestedValue $child 635bc00e11SAndreas Gohr * @return void 645bc00e11SAndreas Gohr */ 655bc00e11SAndreas Gohr public function addChild(NestedValue $child) 665bc00e11SAndreas Gohr { 675bc00e11SAndreas Gohr $this->children[(string)$child] = $child; // ensures uniqueness 685bc00e11SAndreas Gohr } 695bc00e11SAndreas Gohr 705bc00e11SAndreas Gohr /** 715bc00e11SAndreas Gohr * Get all child nodes 725bc00e11SAndreas Gohr * 735bc00e11SAndreas Gohr * @return NestedValue[] 745bc00e11SAndreas Gohr */ 755bc00e11SAndreas Gohr public function getChildren() 765bc00e11SAndreas Gohr { 775bc00e11SAndreas Gohr $children = $this->children; 785bc00e11SAndreas Gohr usort($children, [$this, 'sortChildren']); 795bc00e11SAndreas Gohr return $children; 805bc00e11SAndreas Gohr } 815bc00e11SAndreas Gohr 825bc00e11SAndreas Gohr /** 835bc00e11SAndreas Gohr * Add a result row to this node 845bc00e11SAndreas Gohr * 85*1ee0a2dcSAndreas Gohr * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result 86*1ee0a2dcSAndreas Gohr * 875bc00e11SAndreas Gohr * @param Value[] $row 885bc00e11SAndreas Gohr * @return void 895bc00e11SAndreas Gohr */ 905bc00e11SAndreas Gohr public function addResultRow($row) 915bc00e11SAndreas Gohr { 92*1ee0a2dcSAndreas Gohr // only add unique rows 93*1ee0a2dcSAndreas Gohr $ident = md5(array_reduce($row, function ($carry, $value) { 94*1ee0a2dcSAndreas Gohr return $carry . $value; 95*1ee0a2dcSAndreas Gohr }, '')); 96*1ee0a2dcSAndreas Gohr 97*1ee0a2dcSAndreas Gohr $this->resultRows[$ident] = $row; 985bc00e11SAndreas Gohr } 995bc00e11SAndreas Gohr 1005bc00e11SAndreas Gohr /** 1015bc00e11SAndreas Gohr * Get all result rows stored in this node 1025bc00e11SAndreas Gohr * 1035bc00e11SAndreas Gohr * @return Value[][] 1045bc00e11SAndreas Gohr */ 1055bc00e11SAndreas Gohr public function getResultRows() 1065bc00e11SAndreas Gohr { 107*1ee0a2dcSAndreas Gohr return array_values($this->resultRows); 1085bc00e11SAndreas Gohr } 1095bc00e11SAndreas Gohr 1105bc00e11SAndreas Gohr /** 1115bc00e11SAndreas Gohr * Get a unique key for this node 1125bc00e11SAndreas Gohr * 1135bc00e11SAndreas Gohr * @return string 1145bc00e11SAndreas Gohr */ 1155bc00e11SAndreas Gohr public function __toString() 1165bc00e11SAndreas Gohr { 1175bc00e11SAndreas Gohr if ($this->value === null) return ''; // root node 1185bc00e11SAndreas Gohr return $this->value->__toString() . '-' . $this->depth; 1195bc00e11SAndreas Gohr } 1205bc00e11SAndreas Gohr 1215bc00e11SAndreas Gohr /** 1225bc00e11SAndreas Gohr * Custom comparator to sort the children of this node 1235bc00e11SAndreas Gohr * 1245bc00e11SAndreas Gohr * @param NestedValue $a 1255bc00e11SAndreas Gohr * @param NestedValue $b 1265bc00e11SAndreas Gohr * @return int 1275bc00e11SAndreas Gohr */ 1285bc00e11SAndreas Gohr public function sortChildren(NestedValue $a, NestedValue $b) 1295bc00e11SAndreas Gohr { 1305bc00e11SAndreas Gohr // note: the way NestedResults build the NestedValues, the value object should 1315bc00e11SAndreas Gohr // always contain a single value only. But since the associated column is still 1325bc00e11SAndreas Gohr // a multi-value column, getCompareValue() will still return an array. 1335bc00e11SAndreas Gohr // So here we treat all returns as array and join them with a dash (even though 1345bc00e11SAndreas Gohr // there should never be more than one value in there) 1355bc00e11SAndreas Gohr return Sort::strcmp( 1365bc00e11SAndreas Gohr join('-', (array)$a->getValueObject()->getCompareValue()), 1375bc00e11SAndreas Gohr join('-', (array)$b->getValueObject()->getCompareValue()) 1385bc00e11SAndreas Gohr ); 1395bc00e11SAndreas Gohr } 1405bc00e11SAndreas Gohr 141*1ee0a2dcSAndreas Gohr /** 142*1ee0a2dcSAndreas Gohr * print the tree for debugging 143*1ee0a2dcSAndreas Gohr * 144*1ee0a2dcSAndreas Gohr * @return string 145*1ee0a2dcSAndreas Gohr */ 146*1ee0a2dcSAndreas Gohr public function dump() 147*1ee0a2dcSAndreas Gohr { 148*1ee0a2dcSAndreas Gohr $return = ''; 149*1ee0a2dcSAndreas Gohr 150*1ee0a2dcSAndreas Gohr if ($this->value) { 151*1ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 152*1ee0a2dcSAndreas Gohr $return .= join(', ', (array)$this->value->getDisplayValue()); 153*1ee0a2dcSAndreas Gohr $return .= "\n"; 154*1ee0a2dcSAndreas Gohr } else { 155*1ee0a2dcSAndreas Gohr $return .= "*\n"; 156*1ee0a2dcSAndreas Gohr } 157*1ee0a2dcSAndreas Gohr 158*1ee0a2dcSAndreas Gohr foreach ($this->getResultRows() as $row) { 159*1ee0a2dcSAndreas Gohr $return .= str_pad('', $this->getDepth() * 4, ' '); 160*1ee0a2dcSAndreas Gohr foreach ($row as $value) { 161*1ee0a2dcSAndreas Gohr $return .= ' ' . join(', ', (array)$value->getDisplayValue()); 162*1ee0a2dcSAndreas Gohr } 163*1ee0a2dcSAndreas Gohr $return .= "\n"; 164*1ee0a2dcSAndreas Gohr } 165*1ee0a2dcSAndreas Gohr 166*1ee0a2dcSAndreas Gohr foreach ($this->getChildren() as $child) { 167*1ee0a2dcSAndreas Gohr $return .= $child->dump(); 168*1ee0a2dcSAndreas Gohr } 169*1ee0a2dcSAndreas Gohr 170*1ee0a2dcSAndreas Gohr return $return; 171*1ee0a2dcSAndreas Gohr } 1725bc00e11SAndreas Gohr} 173