1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5/** 6 * Class AggregationList 7 * 8 * @package dokuwiki\plugin\struct\meta 9 */ 10class AggregationList 11{ 12 /** 13 * @var string the page id of the page this is rendered to 14 */ 15 protected $id; 16 /** 17 * @var string the Type of renderer used 18 */ 19 protected $mode; 20 /** 21 * @var \Doku_Renderer the DokuWiki renderer used to create the output 22 */ 23 protected $renderer; 24 /** 25 * @var SearchConfig the configured search - gives access to columns etc. 26 */ 27 protected $searchConfig; 28 29 /** 30 * @var Column[] the list of columns to be displayed 31 */ 32 protected $columns; 33 34 /** 35 * @var Value[][] the search result 36 */ 37 protected $result; 38 39 /** 40 * @var int number of all results 41 */ 42 protected $resultColumnCount; 43 44 /** 45 * Initialize the Aggregation renderer and executes the search 46 * 47 * You need to call @param string $id 48 * @param string $mode 49 * @param \Doku_Renderer $renderer 50 * @param SearchConfig $searchConfig 51 * @see render() on the resulting object. 52 * 53 */ 54 public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig) 55 { 56 $this->id = $id; 57 $this->mode = $mode; 58 $this->renderer = $renderer; 59 $this->searchConfig = $searchConfig; 60 $this->data = $searchConfig->getConf(); 61 $this->columns = $searchConfig->getColumns(); 62 63 $this->result = $this->searchConfig->execute(); 64 $this->resultColumnCount = count($this->columns); 65 $this->resultPIDs = $this->searchConfig->getPids(); 66 } 67 68 /** 69 * Create the list on the renderer 70 */ 71 public function render() 72 { 73 $nestedResult = new NestedResult($this->result); 74 $root = $nestedResult->getRoot($this->data['nesting']); 75 76 $this->startScope(); 77 $this->renderNode($root); 78 $this->finishScope(); 79 } 80 81 /** 82 * Recursively render the result tree 83 * 84 * @param NestedValue $node 85 * @return void 86 */ 87 protected function renderNode(NestedValue $node) 88 { 89 $self = $node->getValueObject(); // null for root node 90 $children = $node->getChildren(); 91 $results = $node->getResultRows(); 92 93 // all our content is in a listitem, unless we are the root node 94 if ($self) { 95 $this->renderer->listitem_open($node->getDepth() + 1); // levels are 1 based 96 } 97 98 // render own value if available 99 if ($self) { 100 $this->renderer->listcontent_open(); 101 $this->renderListItem([$self], $node->getDepth()); // zero based depth 102 $this->renderer->listcontent_close(); 103 } 104 105 // render children or results as sub-list 106 if ($children || $results) { 107 $this->renderer->listu_open(); 108 109 foreach ($children as $child) { 110 $this->renderNode($child); 111 } 112 113 foreach ($results as $result) { 114 $this->renderer->listitem_open($node->getDepth() + 2); // levels are 1 based, this is one deeper 115 $this->renderer->listcontent_open(); 116 $this->renderListItem($result, $node->getDepth() + 1); // zero based depth, one deeper 117 $this->renderer->listcontent_close(); 118 $this->renderer->listitem_close(); 119 } 120 121 $this->renderer->listu_close(); 122 } 123 124 // close listitem if opened 125 if ($self) { 126 $this->renderer->listitem_close(); 127 } 128 } 129 130 /** 131 * Adds additional info to document and renderer in XHTML mode 132 * 133 * @see finishScope() 134 */ 135 protected function startScope() 136 { 137 // wrapping div 138 if ($this->mode != 'xhtml') return; 139 $this->renderer->doc .= "<div class=\"structaggregation listaggregation\">"; 140 } 141 142 /** 143 * Closes anything opened in startScope() 144 * 145 * @see startScope() 146 */ 147 protected function finishScope() 148 { 149 // wrapping div 150 if ($this->mode != 'xhtml') return; 151 $this->renderer->doc .= '</div>'; 152 } 153 154 155 /** 156 * Render the content of a single list item 157 * 158 * @param Value[] $resultrow 159 * @param int $depth The current nesting depth (zero based) 160 */ 161 protected function renderListItem($resultrow, $depth) 162 { 163 $sepbyheaders = $this->searchConfig->getConf()['sepbyheaders']; 164 $headers = $this->searchConfig->getConf()['headers']; 165 166 foreach ($resultrow as $index => $value) { 167 if ($value->isEmpty()) continue; 168 $column = $index + $depth; // the resultrow is shifted by the nesting depth 169 if ($sepbyheaders && !empty($headers[$column])) { 170 $header = $headers[$column]; 171 } else { 172 $header = ''; 173 } 174 175 if ($this->mode === 'xhtml') { 176 $this->renderValueXHTML($value, $header); 177 } else { 178 $this->renderValueGeneric($value, $header); 179 } 180 } 181 } 182 183 /** 184 * Render the given Value in a XHTML renderer 185 * @param Value $value 186 * @param string $header 187 * @return void 188 */ 189 protected function renderValueXHTML($value, $header) 190 { 191 $attributes = [ 192 'data-struct-column' => strtolower($value->getColumn()->getFullQualifiedLabel()), 193 'data-struct-type' => strtolower($value->getColumn()->getType()->getClass()), 194 'class' => 'li', // default dokuwiki content wrapper 195 ]; 196 197 $this->renderer->doc .= sprintf('<div %s>', buildAttributes($attributes)); // wrapper 198 if ($header !== '') { 199 $this->renderer->doc .= sprintf('<span class="struct_header">%s</span> ', hsc($header)); 200 } 201 $this->renderer->doc .= '<div class="struct_value">'; 202 $value->render($this->renderer, $this->mode); 203 $this->renderer->doc .= '</div>'; 204 $this->renderer->doc .= '</div> '; // wrapper 205 } 206 207 /** 208 * Render the given Value in any non-XHTML renderer 209 * @param Value $value 210 * @param string $header 211 * @return void 212 */ 213 protected function renderValueGeneric($value, $header) 214 { 215 $this->renderer->listcontent_open(); 216 if ($header !== '') $this->renderer->cdata($header . ' '); 217 $value->render($this->renderer, $this->mode); 218 $this->renderer->listcontent_close(); 219 } 220} 221