1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5use dokuwiki\Utf8\Sort; 6 7/** 8 * Object to create a tree of values 9 * 10 * You should not create these yourself, but use the NestedResult class instead 11 */ 12class NestedValue 13{ 14 15 /** @var Value */ 16 protected $value; 17 18 /** @var NestedValue[] */ 19 protected $children = []; 20 21 /** @var Value[][] */ 22 protected $resultRows = []; 23 24 /** @var int the nesting depth */ 25 protected $depth; 26 27 /** 28 * Create a nested version of the given value 29 * 30 * @param Value|null $value The value to store, null for root node 31 * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels) 32 */ 33 public function __construct(?Value $value, $depth = 0) 34 { 35 $this->value = $value; 36 $this->depth = $depth; 37 } 38 39 /** 40 * @return int 41 */ 42 public function getDepth() 43 { 44 return $this->depth; 45 } 46 47 /** 48 * Access the stored value 49 * 50 * @return Value|null the value stored in this node, null for root node 51 */ 52 public function getValueObject() 53 { 54 return $this->value; 55 } 56 57 /** 58 * Add a child node 59 * 60 * Nodes with the same key (__toString()) will be overwritten 61 * 62 * @param NestedValue $child 63 * @return void 64 */ 65 public function addChild(NestedValue $child) 66 { 67 $this->children[(string)$child] = $child; // ensures uniqueness 68 } 69 70 /** 71 * Get all child nodes 72 * 73 * @return NestedValue[] 74 */ 75 public function getChildren() 76 { 77 $children = $this->children; 78 usort($children, [$this, 'sortChildren']); 79 return $children; 80 } 81 82 /** 83 * Add a result row to this node 84 * 85 * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result 86 * 87 * @param Value[] $row 88 * @return void 89 */ 90 public function addResultRow($row) 91 { 92 // only add unique rows 93 $ident = md5(array_reduce($row, function ($carry, $value) { 94 return $carry . $value; 95 }, '')); 96 97 $this->resultRows[$ident] = $row; 98 } 99 100 /** 101 * Get all result rows stored in this node 102 * 103 * @return Value[][] 104 */ 105 public function getResultRows() 106 { 107 return array_values($this->resultRows); 108 } 109 110 /** 111 * Get a unique key for this node 112 * 113 * @return string 114 */ 115 public function __toString() 116 { 117 if ($this->value === null) return ''; // root node 118 return $this->value->__toString() . '-' . $this->depth; 119 } 120 121 /** 122 * Custom comparator to sort the children of this node 123 * 124 * @param NestedValue $a 125 * @param NestedValue $b 126 * @return int 127 */ 128 public function sortChildren(NestedValue $a, NestedValue $b) 129 { 130 // note: the way NestedResults build the NestedValues, the value object should 131 // always contain a single value only. But since the associated column is still 132 // a multi-value column, getCompareValue() will still return an array. 133 // So here we treat all returns as array and join them with a dash (even though 134 // there should never be more than one value in there) 135 return Sort::strcmp( 136 join('-', (array)$a->getValueObject()->getCompareValue()), 137 join('-', (array)$b->getValueObject()->getCompareValue()) 138 ); 139 } 140 141 /** 142 * print the tree for debugging 143 * 144 * @return string 145 */ 146 public function dump() 147 { 148 $return = ''; 149 150 if ($this->value) { 151 $return .= str_pad('', $this->getDepth() * 4, ' '); 152 $return .= join(', ', (array)$this->value->getDisplayValue()); 153 $return .= "\n"; 154 } else { 155 $return .= "*\n"; 156 } 157 158 foreach ($this->getResultRows() as $row) { 159 $return .= str_pad('', $this->getDepth() * 4, ' '); 160 foreach ($row as $value) { 161 $return .= ' ' . join(', ', (array)$value->getDisplayValue()); 162 } 163 $return .= "\n"; 164 } 165 166 foreach ($this->getChildren() as $child) { 167 $return .= $child->dump(); 168 } 169 170 return $return; 171 } 172} 173