1*07993756SAndreas Gohr<?php 2*07993756SAndreas Gohr 3*07993756SAndreas Gohrnamespace plugin\struct\meta; 4*07993756SAndreas Gohr 5*07993756SAndreas Gohrclass AggregationTable { 6*07993756SAndreas Gohr 7*07993756SAndreas Gohr /** 8*07993756SAndreas Gohr * @var string the page id of the page this is rendered to 9*07993756SAndreas Gohr */ 10*07993756SAndreas Gohr protected $id; 11*07993756SAndreas Gohr /** 12*07993756SAndreas Gohr * @var string the Type of renderer used 13*07993756SAndreas Gohr */ 14*07993756SAndreas Gohr protected $mode; 15*07993756SAndreas Gohr /** 16*07993756SAndreas Gohr * @var \Doku_Renderer the DokuWiki renderer used to create the output 17*07993756SAndreas Gohr */ 18*07993756SAndreas Gohr protected $renderer; 19*07993756SAndreas Gohr /** 20*07993756SAndreas Gohr * @var SearchConfig the configured search - gives access to columns etc. 21*07993756SAndreas Gohr */ 22*07993756SAndreas Gohr protected $searchConfig; 23*07993756SAndreas Gohr 24*07993756SAndreas Gohr /** 25*07993756SAndreas Gohr * @var Column[] the list of columns to be displayed 26*07993756SAndreas Gohr */ 27*07993756SAndreas Gohr protected $columns; 28*07993756SAndreas Gohr 29*07993756SAndreas Gohr /** 30*07993756SAndreas Gohr * @var Value[][] the search result 31*07993756SAndreas Gohr */ 32*07993756SAndreas Gohr protected $result; 33*07993756SAndreas Gohr 34*07993756SAndreas Gohr /** 35*07993756SAndreas Gohr * @var int number of all results 36*07993756SAndreas Gohr */ 37*07993756SAndreas Gohr protected $resultCount; 38*07993756SAndreas Gohr 39*07993756SAndreas Gohr /** 40*07993756SAndreas Gohr * @var array for summing up columns 41*07993756SAndreas Gohr */ 42*07993756SAndreas Gohr protected $sums; 43*07993756SAndreas Gohr 44*07993756SAndreas Gohr /** 45*07993756SAndreas Gohr * @todo we might be able to get rid of this helper and move this to SearchConfig 46*07993756SAndreas Gohr * @var \helper_plugin_struct_config 47*07993756SAndreas Gohr */ 48*07993756SAndreas Gohr protected $helper; 49*07993756SAndreas Gohr 50*07993756SAndreas Gohr /** 51*07993756SAndreas Gohr * Initialize the Aggregation renderer and executes the search 52*07993756SAndreas Gohr * 53*07993756SAndreas Gohr * You need to call @see render() on the resulting object. 54*07993756SAndreas Gohr * 55*07993756SAndreas Gohr * @param string $id 56*07993756SAndreas Gohr * @param string $mode 57*07993756SAndreas Gohr * @param \Doku_Renderer $renderer 58*07993756SAndreas Gohr * @param SearchConfig $searchConfig 59*07993756SAndreas Gohr */ 60*07993756SAndreas Gohr public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig) { 61*07993756SAndreas Gohr $this->id = $id; 62*07993756SAndreas Gohr $this->mode = $mode; 63*07993756SAndreas Gohr $this->renderer = $renderer; 64*07993756SAndreas Gohr $this->searchConfig = $searchConfig; 65*07993756SAndreas Gohr $this->data = $searchConfig->getConf(); 66*07993756SAndreas Gohr $this->columns = $searchConfig->getColumns(); 67*07993756SAndreas Gohr 68*07993756SAndreas Gohr $this->result = $this->searchConfig->execute(); 69*07993756SAndreas Gohr $this->resultCount = $this->searchConfig->getCount(); 70*07993756SAndreas Gohr $this->helper = plugin_load('helper', 'struct_config'); 71*07993756SAndreas Gohr } 72*07993756SAndreas Gohr 73*07993756SAndreas Gohr /** 74*07993756SAndreas Gohr * Create the table on the renderer 75*07993756SAndreas Gohr */ 76*07993756SAndreas Gohr public function render() { 77*07993756SAndreas Gohr // table open 78*07993756SAndreas Gohr $this->startScope(); 79*07993756SAndreas Gohr $this->showActiveFilters(); 80*07993756SAndreas Gohr $this->renderer->table_open(); 81*07993756SAndreas Gohr 82*07993756SAndreas Gohr // header 83*07993756SAndreas Gohr $this->renderer->tablethead_open(); 84*07993756SAndreas Gohr $this->buildColumnHeaders(); 85*07993756SAndreas Gohr $this->addDynamicFilters(); 86*07993756SAndreas Gohr $this->renderer->tablethead_close(); 87*07993756SAndreas Gohr 88*07993756SAndreas Gohr if($this->resultCount) { 89*07993756SAndreas Gohr // actual data 90*07993756SAndreas Gohr $this->renderRows(); 91*07993756SAndreas Gohr 92*07993756SAndreas Gohr // footer 93*07993756SAndreas Gohr $this->summarize(); 94*07993756SAndreas Gohr $this->addLimitControls(); 95*07993756SAndreas Gohr } else { 96*07993756SAndreas Gohr // nothing found 97*07993756SAndreas Gohr $this->nullRow(); 98*07993756SAndreas Gohr } 99*07993756SAndreas Gohr 100*07993756SAndreas Gohr // table close 101*07993756SAndreas Gohr $this->renderer->table_close(); 102*07993756SAndreas Gohr $this->finishScope(); 103*07993756SAndreas Gohr } 104*07993756SAndreas Gohr 105*07993756SAndreas Gohr /** 106*07993756SAndreas Gohr * Adds additional info to document and renderer in XHTML mode 107*07993756SAndreas Gohr * 108*07993756SAndreas Gohr * @see finishScope() 109*07993756SAndreas Gohr */ 110*07993756SAndreas Gohr protected function startScope() { 111*07993756SAndreas Gohr if($this->mode != 'xhtml') return; 112*07993756SAndreas Gohr 113*07993756SAndreas Gohr // wrapping div 114*07993756SAndreas Gohr $this->renderer->doc .= "<div class=\"structaggregation\">"; 115*07993756SAndreas Gohr 116*07993756SAndreas Gohr // unique identifier for this aggregation 117*07993756SAndreas Gohr $this->renderer->info['struct_table_hash'] = md5(var_export($this->data, true)); 118*07993756SAndreas Gohr } 119*07993756SAndreas Gohr 120*07993756SAndreas Gohr /** 121*07993756SAndreas Gohr * Closes the table and anything opened in startScope() 122*07993756SAndreas Gohr * 123*07993756SAndreas Gohr * @see startScope() 124*07993756SAndreas Gohr */ 125*07993756SAndreas Gohr protected function finishScope() { 126*07993756SAndreas Gohr if($this->mode != 'xhtml') return; 127*07993756SAndreas Gohr 128*07993756SAndreas Gohr // wrapping div 129*07993756SAndreas Gohr $this->renderer->doc .= '</div>'; 130*07993756SAndreas Gohr 131*07993756SAndreas Gohr // remove identifier from renderer again 132*07993756SAndreas Gohr if(isset($this->renderer->info['struct_table_hash'])) { 133*07993756SAndreas Gohr unset($this->renderer->info['struct_table_hash']); 134*07993756SAndreas Gohr } 135*07993756SAndreas Gohr } 136*07993756SAndreas Gohr 137*07993756SAndreas Gohr /** 138*07993756SAndreas Gohr * Displays info about the currently applied filters 139*07993756SAndreas Gohr */ 140*07993756SAndreas Gohr protected function showActiveFilters() { 141*07993756SAndreas Gohr if($this->mode != 'xhtml') return; 142*07993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 143*07993756SAndreas Gohr $filters = $dynamic->getFilters(); 144*07993756SAndreas Gohr if(!$filters) return; 145*07993756SAndreas Gohr 146*07993756SAndreas Gohr $fltrs = array(); 147*07993756SAndreas Gohr foreach($filters as $column => $filter) { 148*07993756SAndreas Gohr list($comp, $value) = $filter; 149*07993756SAndreas Gohr 150*07993756SAndreas Gohr if(strpos($comp, '~') !== false) { 151*07993756SAndreas Gohr if(strpos($comp, '!~') !== false) { 152*07993756SAndreas Gohr $comparator_value = '!~' . str_replace('%', '*', $value); 153*07993756SAndreas Gohr } else { 154*07993756SAndreas Gohr $comparator_value = '~' . str_replace('%', '', $value); 155*07993756SAndreas Gohr } 156*07993756SAndreas Gohr $fltrs[] = $column . $comparator_value; 157*07993756SAndreas Gohr } else { 158*07993756SAndreas Gohr $fltrs[] = $column . $comp . $value; 159*07993756SAndreas Gohr } 160*07993756SAndreas Gohr } 161*07993756SAndreas Gohr 162*07993756SAndreas Gohr $this->renderer->doc .= '<div class="filter">'; 163*07993756SAndreas Gohr $this->renderer->doc .= '<h4>' . sprintf($this->helper->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>'; 164*07993756SAndreas Gohr $this->renderer->doc .= '<div class="resetfilter">'; 165*07993756SAndreas Gohr $this->renderer->internallink($this->id, $this->helper->getLang('tableresetfilter')); 166*07993756SAndreas Gohr $this->renderer->doc .= '</div>'; 167*07993756SAndreas Gohr $this->renderer->doc .= '</div>'; 168*07993756SAndreas Gohr } 169*07993756SAndreas Gohr 170*07993756SAndreas Gohr /** 171*07993756SAndreas Gohr * Shows the column headers with links to sort by column 172*07993756SAndreas Gohr */ 173*07993756SAndreas Gohr protected function buildColumnHeaders() { 174*07993756SAndreas Gohr $this->renderer->tablerow_open(); 175*07993756SAndreas Gohr 176*07993756SAndreas Gohr // additional column for row numbers 177*07993756SAndreas Gohr if($this->data['rownumbers']) { 178*07993756SAndreas Gohr $this->renderer->tableheader_open(); 179*07993756SAndreas Gohr $this->renderer->cdata('#'); 180*07993756SAndreas Gohr $this->renderer->tableheader_close(); 181*07993756SAndreas Gohr } 182*07993756SAndreas Gohr 183*07993756SAndreas Gohr // show all headers 184*07993756SAndreas Gohr foreach($this->data['headers'] as $num => $header) { 185*07993756SAndreas Gohr $column = $this->columns[$num]; 186*07993756SAndreas Gohr 187*07993756SAndreas Gohr // use field label if no header was set 188*07993756SAndreas Gohr if(blank($header)) { 189*07993756SAndreas Gohr if(is_a($column, 'plugin\struct\meta\PageColumn')) { 190*07993756SAndreas Gohr $header = $this->helper->getLang('pagelabel'); // @todo this could be part of PageColumn::getTranslatedLabel 191*07993756SAndreas Gohr } else if(is_a($column, 'plugin\struct\meta\Column')) { 192*07993756SAndreas Gohr $header = $column->getTranslatedLabel(); 193*07993756SAndreas Gohr } else { 194*07993756SAndreas Gohr $header = 'column ' . $num; // this should never happen 195*07993756SAndreas Gohr } 196*07993756SAndreas Gohr } 197*07993756SAndreas Gohr 198*07993756SAndreas Gohr // simple mode first 199*07993756SAndreas Gohr if($this->mode != 'xhtml') { 200*07993756SAndreas Gohr $this->renderer->tableheader_open(); 201*07993756SAndreas Gohr $this->renderer->cdata($header); 202*07993756SAndreas Gohr $this->renderer->tableheader_close(); 203*07993756SAndreas Gohr continue; 204*07993756SAndreas Gohr } 205*07993756SAndreas Gohr 206*07993756SAndreas Gohr // still here? create custom header for more flexibility 207*07993756SAndreas Gohr 208*07993756SAndreas Gohr // width setting 209*07993756SAndreas Gohr $width = ''; 210*07993756SAndreas Gohr if(isset($data['widths'][$num]) && $data['widths'][$num] != '-') { 211*07993756SAndreas Gohr $width = ' style="width: ' . $data['widths'][$num] . ';"'; 212*07993756SAndreas Gohr } 213*07993756SAndreas Gohr 214*07993756SAndreas Gohr // sort indicator and link 215*07993756SAndreas Gohr $sortclass = ''; 216*07993756SAndreas Gohr $sorts = $this->searchConfig->getSorts(); 217*07993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 218*07993756SAndreas Gohr if(isset($sorts[$column->getFullQualifiedLabel()])) { 219*07993756SAndreas Gohr list(, $currentSort) = $sorts[$column->getFullQualifiedLabel()]; 220*07993756SAndreas Gohr if($currentSort[1]) { 221*07993756SAndreas Gohr $sortclass = 'sort-down'; 222*07993756SAndreas Gohr $dynamic->setSort($column, false); 223*07993756SAndreas Gohr } else { 224*07993756SAndreas Gohr $sortclass = 'sort-up'; 225*07993756SAndreas Gohr } 226*07993756SAndreas Gohr } 227*07993756SAndreas Gohr $dynamic->setSort($column, true); 228*07993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 229*07993756SAndreas Gohr 230*07993756SAndreas Gohr // output XHTML header 231*07993756SAndreas Gohr $this->renderer->doc .= "<th $width >"; 232*07993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="' . $sortclass . '" title="' . $this->helper->getLang('sort') . '">' . hsc($header) . '</a>'; 233*07993756SAndreas Gohr $this->renderer->doc .= '</th>'; 234*07993756SAndreas Gohr } 235*07993756SAndreas Gohr 236*07993756SAndreas Gohr $this->renderer->tablerow_close(); 237*07993756SAndreas Gohr } 238*07993756SAndreas Gohr 239*07993756SAndreas Gohr /** 240*07993756SAndreas Gohr * Add input fields for dynamic filtering 241*07993756SAndreas Gohr */ 242*07993756SAndreas Gohr protected function addDynamicFilters() { 243*07993756SAndreas Gohr if($this->mode != 'xhtml') return; 244*07993756SAndreas Gohr if(!$this->data['dynfilters']) return; 245*07993756SAndreas Gohr 246*07993756SAndreas Gohr $this->renderer->doc .= '<tr class="dataflt">'; 247*07993756SAndreas Gohr 248*07993756SAndreas Gohr // add extra column for row numbers 249*07993756SAndreas Gohr if($this->data['rownumbers']) { 250*07993756SAndreas Gohr $this->renderer->doc .= '<th></th>'; 251*07993756SAndreas Gohr } 252*07993756SAndreas Gohr 253*07993756SAndreas Gohr // each column gets a form 254*07993756SAndreas Gohr foreach($this->columns as $column) { 255*07993756SAndreas Gohr $this->renderer->doc .= '<th>'; 256*07993756SAndreas Gohr { 257*07993756SAndreas Gohr $form = new \Doku_Form(array('method' => 'GET', 'action' => wl($this->id))); 258*07993756SAndreas Gohr 259*07993756SAndreas Gohr // current value 260*07993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 261*07993756SAndreas Gohr $filters = $dynamic->getFilters(); 262*07993756SAndreas Gohr if(isset($filters[$column->getFullQualifiedLabel()])) { 263*07993756SAndreas Gohr list(, $current) = $filters[$column->getFullQualifiedLabel()]; 264*07993756SAndreas Gohr $dynamic->removeFilter($column); 265*07993756SAndreas Gohr } else { 266*07993756SAndreas Gohr $current = ''; 267*07993756SAndreas Gohr } 268*07993756SAndreas Gohr 269*07993756SAndreas Gohr // Add current request params 270*07993756SAndreas Gohr $params = $dynamic->getURLParameters(); 271*07993756SAndreas Gohr foreach($params as $key => $val) { 272*07993756SAndreas Gohr $form->addHidden($key, $val); 273*07993756SAndreas Gohr } 274*07993756SAndreas Gohr 275*07993756SAndreas Gohr // add input field 276*07993756SAndreas Gohr $key = $column->getFullQualifiedLabel() . '*~'; 277*07993756SAndreas Gohr $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $current, '')); 278*07993756SAndreas Gohr $this->renderer->doc .= $form->getForm(); 279*07993756SAndreas Gohr } 280*07993756SAndreas Gohr $this->renderer->doc .= '</th>'; 281*07993756SAndreas Gohr } 282*07993756SAndreas Gohr $this->renderer->doc .= '</tr>'; 283*07993756SAndreas Gohr 284*07993756SAndreas Gohr } 285*07993756SAndreas Gohr 286*07993756SAndreas Gohr /** 287*07993756SAndreas Gohr * Display the actual table data 288*07993756SAndreas Gohr */ 289*07993756SAndreas Gohr protected function renderRows() { 290*07993756SAndreas Gohr $this->renderer->tabletbody_open(); 291*07993756SAndreas Gohr foreach($this->result as $rownum => $row) { 292*07993756SAndreas Gohr $this->renderer->tablerow_open(); 293*07993756SAndreas Gohr 294*07993756SAndreas Gohr // row number column 295*07993756SAndreas Gohr if($this->data['rownumbers']) { 296*07993756SAndreas Gohr $this->renderer->tablecell_open(); 297*07993756SAndreas Gohr $this->renderer->doc .= $rownum + 1; 298*07993756SAndreas Gohr $this->renderer->tablecell_close(); 299*07993756SAndreas Gohr } 300*07993756SAndreas Gohr 301*07993756SAndreas Gohr /** @var Value $value */ 302*07993756SAndreas Gohr foreach($row as $colnum => $value) { 303*07993756SAndreas Gohr $this->renderer->tablecell_open(); 304*07993756SAndreas Gohr $value->render($this->renderer, $this->mode); 305*07993756SAndreas Gohr $this->renderer->tablecell_close(); 306*07993756SAndreas Gohr 307*07993756SAndreas Gohr // summarize 308*07993756SAndreas Gohr if($this->data['summarize'] && is_numeric($value->getValue())) { 309*07993756SAndreas Gohr if(!isset($this->sums[$colnum])) { 310*07993756SAndreas Gohr $this->sums[$colnum] = 0; 311*07993756SAndreas Gohr } 312*07993756SAndreas Gohr $this->sums[$colnum] += $value->getValue(); 313*07993756SAndreas Gohr } 314*07993756SAndreas Gohr } 315*07993756SAndreas Gohr $this->renderer->tablerow_close(); 316*07993756SAndreas Gohr } 317*07993756SAndreas Gohr $this->renderer->tabletbody_close(); 318*07993756SAndreas Gohr } 319*07993756SAndreas Gohr 320*07993756SAndreas Gohr /** 321*07993756SAndreas Gohr * Renders an information row for when no results were found 322*07993756SAndreas Gohr */ 323*07993756SAndreas Gohr protected function nullRow() { 324*07993756SAndreas Gohr $this->renderer->tablerow_open(); 325*07993756SAndreas Gohr $this->renderer->tablecell_open(count($this->data['cols']) + $this->data['rownumbers'], 'center'); 326*07993756SAndreas Gohr $this->renderer->cdata($this->helper->getLang('none')); 327*07993756SAndreas Gohr $this->renderer->tablecell_close(); 328*07993756SAndreas Gohr $this->renderer->tablerow_close(); 329*07993756SAndreas Gohr } 330*07993756SAndreas Gohr 331*07993756SAndreas Gohr /** 332*07993756SAndreas Gohr * Add sums if wanted 333*07993756SAndreas Gohr */ 334*07993756SAndreas Gohr protected function summarize() { 335*07993756SAndreas Gohr if($this->data['summarize']) return; 336*07993756SAndreas Gohr 337*07993756SAndreas Gohr $this->renderer->tablerow_open(); 338*07993756SAndreas Gohr $len = count($this->data['cols']); 339*07993756SAndreas Gohr 340*07993756SAndreas Gohr if($this->data['rownumbers']) { 341*07993756SAndreas Gohr $this->renderer->tablecell_open(); 342*07993756SAndreas Gohr $this->renderer->tablecell_close(); 343*07993756SAndreas Gohr } 344*07993756SAndreas Gohr 345*07993756SAndreas Gohr for($i = 0; $i < $len; $i++) { 346*07993756SAndreas Gohr $this->renderer->tablecell_open(1, $this->data['align'][$i]); 347*07993756SAndreas Gohr if(!empty($sums[$i])) { 348*07993756SAndreas Gohr $this->renderer->cdata('∑ ' . $sums[$i]); 349*07993756SAndreas Gohr } else { 350*07993756SAndreas Gohr if($this->mode == 'xhtml') { 351*07993756SAndreas Gohr $this->renderer->doc .= ' '; 352*07993756SAndreas Gohr } 353*07993756SAndreas Gohr } 354*07993756SAndreas Gohr $this->renderer->tablecell_close(); 355*07993756SAndreas Gohr } 356*07993756SAndreas Gohr $this->renderer->tablerow_close(); 357*07993756SAndreas Gohr } 358*07993756SAndreas Gohr 359*07993756SAndreas Gohr /** 360*07993756SAndreas Gohr * Adds pagin controls to the table 361*07993756SAndreas Gohr */ 362*07993756SAndreas Gohr protected function addLimitControls() { 363*07993756SAndreas Gohr if(empty($this->data['limit'])) return; 364*07993756SAndreas Gohr if($this->mode != 'xhtml') ; 365*07993756SAndreas Gohr 366*07993756SAndreas Gohr $this->renderer->tablerow_open(); 367*07993756SAndreas Gohr $this->renderer->tableheader_open((count($this->data['cols']) + ($this->data['rownumbers'] ? 1 : 0))); 368*07993756SAndreas Gohr $offset = $this->data['offset']; 369*07993756SAndreas Gohr 370*07993756SAndreas Gohr // prev link 371*07993756SAndreas Gohr if($offset) { 372*07993756SAndreas Gohr $prev = $offset - $this->data['limit']; 373*07993756SAndreas Gohr if($prev < 0) { 374*07993756SAndreas Gohr $prev = 0; 375*07993756SAndreas Gohr } 376*07993756SAndreas Gohr 377*07993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 378*07993756SAndreas Gohr $dynamic->setOffset($prev); 379*07993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 380*07993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="prev">' . $this->helper->getLang('prev') . '</a>'; 381*07993756SAndreas Gohr } 382*07993756SAndreas Gohr 383*07993756SAndreas Gohr // next link 384*07993756SAndreas Gohr if($this->resultCount > $offset + $this->data['limit']) { 385*07993756SAndreas Gohr $next = $offset + $this->data['limit']; 386*07993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 387*07993756SAndreas Gohr $dynamic->setOffset($next); 388*07993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 389*07993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="next">' . $this->helper->getLang('next') . '</a>'; 390*07993756SAndreas Gohr } 391*07993756SAndreas Gohr 392*07993756SAndreas Gohr $this->renderer->tableheader_close(); 393*07993756SAndreas Gohr $this->renderer->tablerow_close(); 394*07993756SAndreas Gohr } 395*07993756SAndreas Gohr} 396