107993756SAndreas Gohr<?php 207993756SAndreas Gohr 307993756SAndreas Gohrnamespace plugin\struct\meta; 407993756SAndreas Gohr 507993756SAndreas Gohrclass AggregationTable { 607993756SAndreas Gohr 707993756SAndreas Gohr /** 807993756SAndreas Gohr * @var string the page id of the page this is rendered to 907993756SAndreas Gohr */ 1007993756SAndreas Gohr protected $id; 1107993756SAndreas Gohr /** 1207993756SAndreas Gohr * @var string the Type of renderer used 1307993756SAndreas Gohr */ 1407993756SAndreas Gohr protected $mode; 1507993756SAndreas Gohr /** 1607993756SAndreas Gohr * @var \Doku_Renderer the DokuWiki renderer used to create the output 1707993756SAndreas Gohr */ 1807993756SAndreas Gohr protected $renderer; 1907993756SAndreas Gohr /** 2007993756SAndreas Gohr * @var SearchConfig the configured search - gives access to columns etc. 2107993756SAndreas Gohr */ 2207993756SAndreas Gohr protected $searchConfig; 2307993756SAndreas Gohr 2407993756SAndreas Gohr /** 2507993756SAndreas Gohr * @var Column[] the list of columns to be displayed 2607993756SAndreas Gohr */ 2707993756SAndreas Gohr protected $columns; 2807993756SAndreas Gohr 2907993756SAndreas Gohr /** 3007993756SAndreas Gohr * @var Value[][] the search result 3107993756SAndreas Gohr */ 3207993756SAndreas Gohr protected $result; 3307993756SAndreas Gohr 3407993756SAndreas Gohr /** 3507993756SAndreas Gohr * @var int number of all results 3607993756SAndreas Gohr */ 3707993756SAndreas Gohr protected $resultCount; 3807993756SAndreas Gohr 3907993756SAndreas Gohr /** 4007993756SAndreas Gohr * @var array for summing up columns 4107993756SAndreas Gohr */ 4207993756SAndreas Gohr protected $sums; 4307993756SAndreas Gohr 4407993756SAndreas Gohr /** 4507993756SAndreas Gohr * @todo we might be able to get rid of this helper and move this to SearchConfig 4607993756SAndreas Gohr * @var \helper_plugin_struct_config 4707993756SAndreas Gohr */ 4807993756SAndreas Gohr protected $helper; 4907993756SAndreas Gohr 5007993756SAndreas Gohr /** 5107993756SAndreas Gohr * Initialize the Aggregation renderer and executes the search 5207993756SAndreas Gohr * 5307993756SAndreas Gohr * You need to call @see render() on the resulting object. 5407993756SAndreas Gohr * 5507993756SAndreas Gohr * @param string $id 5607993756SAndreas Gohr * @param string $mode 5707993756SAndreas Gohr * @param \Doku_Renderer $renderer 5807993756SAndreas Gohr * @param SearchConfig $searchConfig 5907993756SAndreas Gohr */ 6007993756SAndreas Gohr public function __construct($id, $mode, \Doku_Renderer $renderer, SearchConfig $searchConfig) { 6107993756SAndreas Gohr $this->id = $id; 6207993756SAndreas Gohr $this->mode = $mode; 6307993756SAndreas Gohr $this->renderer = $renderer; 6407993756SAndreas Gohr $this->searchConfig = $searchConfig; 6507993756SAndreas Gohr $this->data = $searchConfig->getConf(); 6607993756SAndreas Gohr $this->columns = $searchConfig->getColumns(); 6707993756SAndreas Gohr 6807993756SAndreas Gohr $this->result = $this->searchConfig->execute(); 6907993756SAndreas Gohr $this->resultCount = $this->searchConfig->getCount(); 7007993756SAndreas Gohr $this->helper = plugin_load('helper', 'struct_config'); 7107993756SAndreas Gohr } 7207993756SAndreas Gohr 7307993756SAndreas Gohr /** 7407993756SAndreas Gohr * Create the table on the renderer 7507993756SAndreas Gohr */ 7607993756SAndreas Gohr public function render() { 7707993756SAndreas Gohr // table open 7807993756SAndreas Gohr $this->startScope(); 79*986ab7e6SAndreas Gohr $this->renderActiveFilters(); 8007993756SAndreas Gohr $this->renderer->table_open(); 8107993756SAndreas Gohr 8207993756SAndreas Gohr // header 8307993756SAndreas Gohr $this->renderer->tablethead_open(); 84*986ab7e6SAndreas Gohr $this->renderColumnHeaders(); 85*986ab7e6SAndreas Gohr $this->renderDynamicFilters(); 8607993756SAndreas Gohr $this->renderer->tablethead_close(); 8707993756SAndreas Gohr 8807993756SAndreas Gohr if($this->resultCount) { 8907993756SAndreas Gohr // actual data 90*986ab7e6SAndreas Gohr $this->renderResult(); 9107993756SAndreas Gohr 9207993756SAndreas Gohr // footer 93*986ab7e6SAndreas Gohr $this->renderSums(); 94*986ab7e6SAndreas Gohr $this->renderPagingControls(); 9507993756SAndreas Gohr } else { 9607993756SAndreas Gohr // nothing found 97*986ab7e6SAndreas Gohr $this->renderEmptyResult(); 9807993756SAndreas Gohr } 9907993756SAndreas Gohr 10007993756SAndreas Gohr // table close 10107993756SAndreas Gohr $this->renderer->table_close(); 10207993756SAndreas Gohr $this->finishScope(); 10307993756SAndreas Gohr } 10407993756SAndreas Gohr 10507993756SAndreas Gohr /** 10607993756SAndreas Gohr * Adds additional info to document and renderer in XHTML mode 10707993756SAndreas Gohr * 10807993756SAndreas Gohr * @see finishScope() 10907993756SAndreas Gohr */ 11007993756SAndreas Gohr protected function startScope() { 11107993756SAndreas Gohr if($this->mode != 'xhtml') return; 11207993756SAndreas Gohr 11307993756SAndreas Gohr // wrapping div 11407993756SAndreas Gohr $this->renderer->doc .= "<div class=\"structaggregation\">"; 11507993756SAndreas Gohr 11607993756SAndreas Gohr // unique identifier for this aggregation 11707993756SAndreas Gohr $this->renderer->info['struct_table_hash'] = md5(var_export($this->data, true)); 11807993756SAndreas Gohr } 11907993756SAndreas Gohr 12007993756SAndreas Gohr /** 12107993756SAndreas Gohr * Closes the table and anything opened in startScope() 12207993756SAndreas Gohr * 12307993756SAndreas Gohr * @see startScope() 12407993756SAndreas Gohr */ 12507993756SAndreas Gohr protected function finishScope() { 12607993756SAndreas Gohr if($this->mode != 'xhtml') return; 12707993756SAndreas Gohr 12807993756SAndreas Gohr // wrapping div 12907993756SAndreas Gohr $this->renderer->doc .= '</div>'; 13007993756SAndreas Gohr 13107993756SAndreas Gohr // remove identifier from renderer again 13207993756SAndreas Gohr if(isset($this->renderer->info['struct_table_hash'])) { 13307993756SAndreas Gohr unset($this->renderer->info['struct_table_hash']); 13407993756SAndreas Gohr } 13507993756SAndreas Gohr } 13607993756SAndreas Gohr 13707993756SAndreas Gohr /** 13807993756SAndreas Gohr * Displays info about the currently applied filters 13907993756SAndreas Gohr */ 140*986ab7e6SAndreas Gohr protected function renderActiveFilters() { 14107993756SAndreas Gohr if($this->mode != 'xhtml') return; 14207993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 14307993756SAndreas Gohr $filters = $dynamic->getFilters(); 14407993756SAndreas Gohr if(!$filters) return; 14507993756SAndreas Gohr 14607993756SAndreas Gohr $fltrs = array(); 14707993756SAndreas Gohr foreach($filters as $column => $filter) { 14807993756SAndreas Gohr list($comp, $value) = $filter; 14907993756SAndreas Gohr 15007993756SAndreas Gohr if(strpos($comp, '~') !== false) { 15107993756SAndreas Gohr if(strpos($comp, '!~') !== false) { 15207993756SAndreas Gohr $comparator_value = '!~' . str_replace('%', '*', $value); 15307993756SAndreas Gohr } else { 15407993756SAndreas Gohr $comparator_value = '~' . str_replace('%', '', $value); 15507993756SAndreas Gohr } 15607993756SAndreas Gohr $fltrs[] = $column . $comparator_value; 15707993756SAndreas Gohr } else { 15807993756SAndreas Gohr $fltrs[] = $column . $comp . $value; 15907993756SAndreas Gohr } 16007993756SAndreas Gohr } 16107993756SAndreas Gohr 16207993756SAndreas Gohr $this->renderer->doc .= '<div class="filter">'; 16307993756SAndreas Gohr $this->renderer->doc .= '<h4>' . sprintf($this->helper->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>'; 16407993756SAndreas Gohr $this->renderer->doc .= '<div class="resetfilter">'; 16507993756SAndreas Gohr $this->renderer->internallink($this->id, $this->helper->getLang('tableresetfilter')); 16607993756SAndreas Gohr $this->renderer->doc .= '</div>'; 16707993756SAndreas Gohr $this->renderer->doc .= '</div>'; 16807993756SAndreas Gohr } 16907993756SAndreas Gohr 17007993756SAndreas Gohr /** 17107993756SAndreas Gohr * Shows the column headers with links to sort by column 17207993756SAndreas Gohr */ 173*986ab7e6SAndreas Gohr protected function renderColumnHeaders() { 17407993756SAndreas Gohr $this->renderer->tablerow_open(); 17507993756SAndreas Gohr 17607993756SAndreas Gohr // additional column for row numbers 17707993756SAndreas Gohr if($this->data['rownumbers']) { 17807993756SAndreas Gohr $this->renderer->tableheader_open(); 17907993756SAndreas Gohr $this->renderer->cdata('#'); 18007993756SAndreas Gohr $this->renderer->tableheader_close(); 18107993756SAndreas Gohr } 18207993756SAndreas Gohr 18307993756SAndreas Gohr // show all headers 18407993756SAndreas Gohr foreach($this->data['headers'] as $num => $header) { 18507993756SAndreas Gohr $column = $this->columns[$num]; 18607993756SAndreas Gohr 18707993756SAndreas Gohr // use field label if no header was set 18807993756SAndreas Gohr if(blank($header)) { 18907993756SAndreas Gohr if(is_a($column, 'plugin\struct\meta\PageColumn')) { 19007993756SAndreas Gohr $header = $this->helper->getLang('pagelabel'); // @todo this could be part of PageColumn::getTranslatedLabel 19107993756SAndreas Gohr } else if(is_a($column, 'plugin\struct\meta\Column')) { 19207993756SAndreas Gohr $header = $column->getTranslatedLabel(); 19307993756SAndreas Gohr } else { 19407993756SAndreas Gohr $header = 'column ' . $num; // this should never happen 19507993756SAndreas Gohr } 19607993756SAndreas Gohr } 19707993756SAndreas Gohr 19807993756SAndreas Gohr // simple mode first 19907993756SAndreas Gohr if($this->mode != 'xhtml') { 20007993756SAndreas Gohr $this->renderer->tableheader_open(); 20107993756SAndreas Gohr $this->renderer->cdata($header); 20207993756SAndreas Gohr $this->renderer->tableheader_close(); 20307993756SAndreas Gohr continue; 20407993756SAndreas Gohr } 20507993756SAndreas Gohr 20607993756SAndreas Gohr // still here? create custom header for more flexibility 20707993756SAndreas Gohr 20807993756SAndreas Gohr // width setting 20907993756SAndreas Gohr $width = ''; 21007993756SAndreas Gohr if(isset($data['widths'][$num]) && $data['widths'][$num] != '-') { 21107993756SAndreas Gohr $width = ' style="width: ' . $data['widths'][$num] . ';"'; 21207993756SAndreas Gohr } 21307993756SAndreas Gohr 21407993756SAndreas Gohr // sort indicator and link 21507993756SAndreas Gohr $sortclass = ''; 21607993756SAndreas Gohr $sorts = $this->searchConfig->getSorts(); 21707993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 21807993756SAndreas Gohr if(isset($sorts[$column->getFullQualifiedLabel()])) { 21907993756SAndreas Gohr list(, $currentSort) = $sorts[$column->getFullQualifiedLabel()]; 22007993756SAndreas Gohr if($currentSort[1]) { 22107993756SAndreas Gohr $sortclass = 'sort-down'; 22207993756SAndreas Gohr $dynamic->setSort($column, false); 22307993756SAndreas Gohr } else { 22407993756SAndreas Gohr $sortclass = 'sort-up'; 22507993756SAndreas Gohr } 22607993756SAndreas Gohr } 22707993756SAndreas Gohr $dynamic->setSort($column, true); 22807993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 22907993756SAndreas Gohr 23007993756SAndreas Gohr // output XHTML header 23107993756SAndreas Gohr $this->renderer->doc .= "<th $width >"; 23207993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="' . $sortclass . '" title="' . $this->helper->getLang('sort') . '">' . hsc($header) . '</a>'; 23307993756SAndreas Gohr $this->renderer->doc .= '</th>'; 23407993756SAndreas Gohr } 23507993756SAndreas Gohr 23607993756SAndreas Gohr $this->renderer->tablerow_close(); 23707993756SAndreas Gohr } 23807993756SAndreas Gohr 23907993756SAndreas Gohr /** 24007993756SAndreas Gohr * Add input fields for dynamic filtering 24107993756SAndreas Gohr */ 242*986ab7e6SAndreas Gohr protected function renderDynamicFilters() { 24307993756SAndreas Gohr if($this->mode != 'xhtml') return; 24407993756SAndreas Gohr if(!$this->data['dynfilters']) return; 24507993756SAndreas Gohr 24607993756SAndreas Gohr $this->renderer->doc .= '<tr class="dataflt">'; 24707993756SAndreas Gohr 24807993756SAndreas Gohr // add extra column for row numbers 24907993756SAndreas Gohr if($this->data['rownumbers']) { 25007993756SAndreas Gohr $this->renderer->doc .= '<th></th>'; 25107993756SAndreas Gohr } 25207993756SAndreas Gohr 25307993756SAndreas Gohr // each column gets a form 25407993756SAndreas Gohr foreach($this->columns as $column) { 25507993756SAndreas Gohr $this->renderer->doc .= '<th>'; 25607993756SAndreas Gohr { 25707993756SAndreas Gohr $form = new \Doku_Form(array('method' => 'GET', 'action' => wl($this->id))); 25807993756SAndreas Gohr 25907993756SAndreas Gohr // current value 26007993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 26107993756SAndreas Gohr $filters = $dynamic->getFilters(); 26207993756SAndreas Gohr if(isset($filters[$column->getFullQualifiedLabel()])) { 26307993756SAndreas Gohr list(, $current) = $filters[$column->getFullQualifiedLabel()]; 26407993756SAndreas Gohr $dynamic->removeFilter($column); 26507993756SAndreas Gohr } else { 26607993756SAndreas Gohr $current = ''; 26707993756SAndreas Gohr } 26807993756SAndreas Gohr 26907993756SAndreas Gohr // Add current request params 27007993756SAndreas Gohr $params = $dynamic->getURLParameters(); 27107993756SAndreas Gohr foreach($params as $key => $val) { 27207993756SAndreas Gohr $form->addHidden($key, $val); 27307993756SAndreas Gohr } 27407993756SAndreas Gohr 27507993756SAndreas Gohr // add input field 27607993756SAndreas Gohr $key = $column->getFullQualifiedLabel() . '*~'; 27707993756SAndreas Gohr $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $current, '')); 27807993756SAndreas Gohr $this->renderer->doc .= $form->getForm(); 27907993756SAndreas Gohr } 28007993756SAndreas Gohr $this->renderer->doc .= '</th>'; 28107993756SAndreas Gohr } 28207993756SAndreas Gohr $this->renderer->doc .= '</tr>'; 28307993756SAndreas Gohr 28407993756SAndreas Gohr } 28507993756SAndreas Gohr 28607993756SAndreas Gohr /** 28707993756SAndreas Gohr * Display the actual table data 28807993756SAndreas Gohr */ 289*986ab7e6SAndreas Gohr protected function renderResult() { 29007993756SAndreas Gohr $this->renderer->tabletbody_open(); 29107993756SAndreas Gohr foreach($this->result as $rownum => $row) { 29207993756SAndreas Gohr $this->renderer->tablerow_open(); 29307993756SAndreas Gohr 29407993756SAndreas Gohr // row number column 29507993756SAndreas Gohr if($this->data['rownumbers']) { 29607993756SAndreas Gohr $this->renderer->tablecell_open(); 29707993756SAndreas Gohr $this->renderer->doc .= $rownum + 1; 29807993756SAndreas Gohr $this->renderer->tablecell_close(); 29907993756SAndreas Gohr } 30007993756SAndreas Gohr 30107993756SAndreas Gohr /** @var Value $value */ 30207993756SAndreas Gohr foreach($row as $colnum => $value) { 30307993756SAndreas Gohr $this->renderer->tablecell_open(); 30407993756SAndreas Gohr $value->render($this->renderer, $this->mode); 30507993756SAndreas Gohr $this->renderer->tablecell_close(); 30607993756SAndreas Gohr 30707993756SAndreas Gohr // summarize 30807993756SAndreas Gohr if($this->data['summarize'] && is_numeric($value->getValue())) { 30907993756SAndreas Gohr if(!isset($this->sums[$colnum])) { 31007993756SAndreas Gohr $this->sums[$colnum] = 0; 31107993756SAndreas Gohr } 31207993756SAndreas Gohr $this->sums[$colnum] += $value->getValue(); 31307993756SAndreas Gohr } 31407993756SAndreas Gohr } 31507993756SAndreas Gohr $this->renderer->tablerow_close(); 31607993756SAndreas Gohr } 31707993756SAndreas Gohr $this->renderer->tabletbody_close(); 31807993756SAndreas Gohr } 31907993756SAndreas Gohr 32007993756SAndreas Gohr /** 32107993756SAndreas Gohr * Renders an information row for when no results were found 32207993756SAndreas Gohr */ 323*986ab7e6SAndreas Gohr protected function renderEmptyResult() { 32407993756SAndreas Gohr $this->renderer->tablerow_open(); 32507993756SAndreas Gohr $this->renderer->tablecell_open(count($this->data['cols']) + $this->data['rownumbers'], 'center'); 32607993756SAndreas Gohr $this->renderer->cdata($this->helper->getLang('none')); 32707993756SAndreas Gohr $this->renderer->tablecell_close(); 32807993756SAndreas Gohr $this->renderer->tablerow_close(); 32907993756SAndreas Gohr } 33007993756SAndreas Gohr 33107993756SAndreas Gohr /** 33207993756SAndreas Gohr * Add sums if wanted 33307993756SAndreas Gohr */ 334*986ab7e6SAndreas Gohr protected function renderSums() { 33507993756SAndreas Gohr if($this->data['summarize']) return; 33607993756SAndreas Gohr 33707993756SAndreas Gohr $this->renderer->tablerow_open(); 33807993756SAndreas Gohr $len = count($this->data['cols']); 33907993756SAndreas Gohr 34007993756SAndreas Gohr if($this->data['rownumbers']) { 34107993756SAndreas Gohr $this->renderer->tablecell_open(); 34207993756SAndreas Gohr $this->renderer->tablecell_close(); 34307993756SAndreas Gohr } 34407993756SAndreas Gohr 34507993756SAndreas Gohr for($i = 0; $i < $len; $i++) { 34607993756SAndreas Gohr $this->renderer->tablecell_open(1, $this->data['align'][$i]); 34707993756SAndreas Gohr if(!empty($sums[$i])) { 34807993756SAndreas Gohr $this->renderer->cdata('∑ ' . $sums[$i]); 34907993756SAndreas Gohr } else { 35007993756SAndreas Gohr if($this->mode == 'xhtml') { 35107993756SAndreas Gohr $this->renderer->doc .= ' '; 35207993756SAndreas Gohr } 35307993756SAndreas Gohr } 35407993756SAndreas Gohr $this->renderer->tablecell_close(); 35507993756SAndreas Gohr } 35607993756SAndreas Gohr $this->renderer->tablerow_close(); 35707993756SAndreas Gohr } 35807993756SAndreas Gohr 35907993756SAndreas Gohr /** 360*986ab7e6SAndreas Gohr * Adds paging controls to the table 36107993756SAndreas Gohr */ 362*986ab7e6SAndreas Gohr protected function renderPagingControls() { 36307993756SAndreas Gohr if(empty($this->data['limit'])) return; 36407993756SAndreas Gohr if($this->mode != 'xhtml') ; 36507993756SAndreas Gohr 36607993756SAndreas Gohr $this->renderer->tablerow_open(); 36707993756SAndreas Gohr $this->renderer->tableheader_open((count($this->data['cols']) + ($this->data['rownumbers'] ? 1 : 0))); 36807993756SAndreas Gohr $offset = $this->data['offset']; 36907993756SAndreas Gohr 37007993756SAndreas Gohr // prev link 37107993756SAndreas Gohr if($offset) { 37207993756SAndreas Gohr $prev = $offset - $this->data['limit']; 37307993756SAndreas Gohr if($prev < 0) { 37407993756SAndreas Gohr $prev = 0; 37507993756SAndreas Gohr } 37607993756SAndreas Gohr 37707993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 37807993756SAndreas Gohr $dynamic->setOffset($prev); 37907993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 38007993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="prev">' . $this->helper->getLang('prev') . '</a>'; 38107993756SAndreas Gohr } 38207993756SAndreas Gohr 38307993756SAndreas Gohr // next link 38407993756SAndreas Gohr if($this->resultCount > $offset + $this->data['limit']) { 38507993756SAndreas Gohr $next = $offset + $this->data['limit']; 38607993756SAndreas Gohr $dynamic = $this->searchConfig->getDynamicParameters(); 38707993756SAndreas Gohr $dynamic->setOffset($next); 38807993756SAndreas Gohr $link = wl($this->id, $dynamic->getURLParameters()); 38907993756SAndreas Gohr $this->renderer->doc .= '<a href="' . $link . '" class="next">' . $this->helper->getLang('next') . '</a>'; 39007993756SAndreas Gohr } 39107993756SAndreas Gohr 39207993756SAndreas Gohr $this->renderer->tableheader_close(); 39307993756SAndreas Gohr $this->renderer->tablerow_close(); 39407993756SAndreas Gohr } 39507993756SAndreas Gohr} 396