1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5use dokuwiki\plugin\struct\types\Text; 6use dokuwiki\Utf8\PhpString; 7 8/** 9 * This class builds a nested tree from a search result 10 * 11 * This is used to create the nested output in the AggregationList 12 */ 13class NestedResult 14{ 15 16 /** @var NestedValue[] */ 17 protected $nodes = []; 18 19 /** @var NestedValue[] */ 20 protected $indexNodes = []; 21 22 /** @var Value[][] */ 23 protected $result; 24 25 /** 26 * @param Value[][] $result the original search result 27 * @return void 28 */ 29 public function __construct($result) 30 { 31 $this->result = $result; 32 } 33 34 /** 35 * Get the nested result 36 * 37 * @param int $nesting the nesting level to use 38 * @param int $index the number of characters to use for indexing 39 * @return NestedValue the root node of the nested tree 40 */ 41 public function getRoot($nesting, $index = 0) 42 { 43 $this->nodes = []; 44 $root = new NestedValue(null, -1); 45 46 if (!$this->result) return $root; 47 48 foreach ($this->result as $row) { 49 $this->nestBranch($root, $row, $nesting); 50 } 51 52 $root = $this->createIndex($root, $index); 53 return $root; 54 } 55 56 /** 57 * Add a top level index to the tree 58 * 59 * @param NestedValue $root Root node of the tree 60 * @param int $index Number of characters to use for indexing 61 * @return NestedValue new root node 62 */ 63 protected function createIndex(NestedValue $root, $index) 64 { 65 if (!$index) return $root; 66 $this->indexNodes = []; 67 68 $children = $root->getChildren(); 69 $resultRows = $root->getResultRows(); 70 if ($children) { 71 // there are children, so we are a nested result 72 foreach ($children as $child) { 73 $indexValue = $child->getValueObject(); 74 $indexNode = $this->getIndexNode($indexValue, $index); 75 $indexNode->addChild($child); 76 $child->setDepth(1); // increase child's depth from 0 to 1 77 } 78 } elseif ($resultRows) { 79 // no children, so we are currently a flat result 80 foreach ($resultRows as $row) { 81 $indexValue = $row[0]; 82 $indexNode = $this->getIndexNode($indexValue, $index); 83 $indexNode->addResultRow($row); 84 } 85 } 86 87 // now all results are added to index nodes - use them as children 88 $newRoot = new NestedValue(null, -1); 89 foreach ($this->indexNodes as $node) { 90 $newRoot->addChild($node); 91 } 92 return $newRoot; 93 } 94 95 /** 96 * Creates nested nodes for a given result row 97 * 98 * Splits up multi Values into separate nodes, when used in nesting 99 * 100 * @param Value[] $row current result row to work on 101 * @param int $nesting number of wanted nesting levels 102 * @param int $depth current nesting depth (used in recursion) 103 */ 104 protected function nestBranch(NestedValue $parent, $row, $nesting, $depth = 0) 105 { 106 // nesting level reached, add row and return 107 if ($depth >= $nesting) { 108 $parent->addResultRow($row); 109 return; 110 } 111 112 $valObj = array_shift($row); 113 if (!$valObj) return; // no more values to nest, usually shouldn't happen 114 115 if ($valObj->getColumn()->isMulti()) { 116 // split up multi values into separate nodes 117 $values = $valObj->getValue(); 118 foreach ($values as $value) { 119 $newValue = new Value($valObj->getColumn(), $value); 120 $node = $this->getNodeForValue($newValue, $depth); 121 $parent->addChild($node); 122 $this->nestBranch($node, $row, $nesting, $depth + 1); 123 } 124 } else { 125 $node = $this->getNodeForValue($valObj, $depth); 126 $parent->addChild($node); 127 $this->nestBranch($node, $row, $nesting, $depth + 1); 128 129 } 130 } 131 132 /** 133 * Create or get existing Node from the tree 134 * 135 * @param Value $value 136 * @param int $depth 137 * @return NestedValue 138 */ 139 protected function getNodeForValue(Value $value, $depth) 140 { 141 $node = new NestedValue($value, $depth); 142 $key = (string)$node; 143 if (!isset($this->nodes[$key])) { 144 $this->nodes[$key] = $node; 145 } 146 return $this->nodes[$key]; 147 } 148 149 /** 150 * Create or get an existing Node for indexing 151 * 152 * @param Value $value 153 * @param int $index 154 * @return NestedValue 155 */ 156 protected function getIndexNode(Value $value, $index) 157 { 158 $compare = $value->getCompareValue(); 159 if (is_array($compare)) $compare = $compare[0]; 160 $key = PhpString::strtoupper(PhpString::substr($compare, 0, $index)); 161 162 if (!isset($this->indexNodes[$key])) { 163 $col = new Column( 164 0, 165 new Text([], '%index%', false), 166 -1, 167 true, 168 $value->getColumn()->getTable() 169 ); 170 $this->indexNodes[$key] = new NestedValue(new Value($col, $key), 0); 171 } 172 173 return $this->indexNodes[$key]; 174 } 175} 176 177 178