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