diff --git a/meta/AggregationTable.php b/meta/AggregationTable.php new file mode 100644 index 0000000..24851a0 --- /dev/null +++ b/meta/AggregationTable.php @@ -0,0 +1,395 @@ +id = $id; + $this->mode = $mode; + $this->renderer = $renderer; + $this->searchConfig = $searchConfig; + $this->data = $searchConfig->getConf(); + $this->columns = $searchConfig->getColumns(); + + $this->result = $this->searchConfig->execute(); + $this->resultCount = $this->searchConfig->getCount(); + $this->helper = plugin_load('helper', 'struct_config'); + } + + /** + * Create the table on the renderer + */ + public function render() { + // table open + $this->startScope(); + $this->showActiveFilters(); + $this->renderer->table_open(); + + // header + $this->renderer->tablethead_open(); + $this->buildColumnHeaders(); + $this->addDynamicFilters(); + $this->renderer->tablethead_close(); + + if($this->resultCount) { + // actual data + $this->renderRows(); + + // footer + $this->summarize(); + $this->addLimitControls(); + } else { + // nothing found + $this->nullRow(); + } + + // table close + $this->renderer->table_close(); + $this->finishScope(); + } + + /** + * Adds additional info to document and renderer in XHTML mode + * + * @see finishScope() + */ + protected function startScope() { + if($this->mode != 'xhtml') return; + + // wrapping div + $this->renderer->doc .= "
"; + + // unique identifier for this aggregation + $this->renderer->info['struct_table_hash'] = md5(var_export($this->data, true)); + } + + /** + * Closes the table and anything opened in startScope() + * + * @see startScope() + */ + protected function finishScope() { + if($this->mode != 'xhtml') return; + + // wrapping div + $this->renderer->doc .= '
'; + + // remove identifier from renderer again + if(isset($this->renderer->info['struct_table_hash'])) { + unset($this->renderer->info['struct_table_hash']); + } + } + + /** + * Displays info about the currently applied filters + */ + protected function showActiveFilters() { + if($this->mode != 'xhtml') return; + $dynamic = $this->searchConfig->getDynamicParameters(); + $filters = $dynamic->getFilters(); + if(!$filters) return; + + $fltrs = array(); + foreach($filters as $column => $filter) { + list($comp, $value) = $filter; + + if(strpos($comp, '~') !== false) { + if(strpos($comp, '!~') !== false) { + $comparator_value = '!~' . str_replace('%', '*', $value); + } else { + $comparator_value = '~' . str_replace('%', '', $value); + } + $fltrs[] = $column . $comparator_value; + } else { + $fltrs[] = $column . $comp . $value; + } + } + + $this->renderer->doc .= '
'; + $this->renderer->doc .= '

' . sprintf($this->helper->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '

'; + $this->renderer->doc .= '
'; + $this->renderer->internallink($this->id, $this->helper->getLang('tableresetfilter')); + $this->renderer->doc .= '
'; + $this->renderer->doc .= '
'; + } + + /** + * Shows the column headers with links to sort by column + */ + protected function buildColumnHeaders() { + $this->renderer->tablerow_open(); + + // additional column for row numbers + if($this->data['rownumbers']) { + $this->renderer->tableheader_open(); + $this->renderer->cdata('#'); + $this->renderer->tableheader_close(); + } + + // show all headers + foreach($this->data['headers'] as $num => $header) { + $column = $this->columns[$num]; + + // use field label if no header was set + if(blank($header)) { + if(is_a($column, 'plugin\struct\meta\PageColumn')) { + $header = $this->helper->getLang('pagelabel'); // @todo this could be part of PageColumn::getTranslatedLabel + } else if(is_a($column, 'plugin\struct\meta\Column')) { + $header = $column->getTranslatedLabel(); + } else { + $header = 'column ' . $num; // this should never happen + } + } + + // simple mode first + if($this->mode != 'xhtml') { + $this->renderer->tableheader_open(); + $this->renderer->cdata($header); + $this->renderer->tableheader_close(); + continue; + } + + // still here? create custom header for more flexibility + + // width setting + $width = ''; + if(isset($data['widths'][$num]) && $data['widths'][$num] != '-') { + $width = ' style="width: ' . $data['widths'][$num] . ';"'; + } + + // sort indicator and link + $sortclass = ''; + $sorts = $this->searchConfig->getSorts(); + $dynamic = $this->searchConfig->getDynamicParameters(); + if(isset($sorts[$column->getFullQualifiedLabel()])) { + list(, $currentSort) = $sorts[$column->getFullQualifiedLabel()]; + if($currentSort[1]) { + $sortclass = 'sort-down'; + $dynamic->setSort($column, false); + } else { + $sortclass = 'sort-up'; + } + } + $dynamic->setSort($column, true); + $link = wl($this->id, $dynamic->getURLParameters()); + + // output XHTML header + $this->renderer->doc .= ""; + $this->renderer->doc .= '' . hsc($header) . ''; + $this->renderer->doc .= ''; + } + + $this->renderer->tablerow_close(); + } + + /** + * Add input fields for dynamic filtering + */ + protected function addDynamicFilters() { + if($this->mode != 'xhtml') return; + if(!$this->data['dynfilters']) return; + + $this->renderer->doc .= ''; + + // add extra column for row numbers + if($this->data['rownumbers']) { + $this->renderer->doc .= ''; + } + + // each column gets a form + foreach($this->columns as $column) { + $this->renderer->doc .= ''; + { + $form = new \Doku_Form(array('method' => 'GET', 'action' => wl($this->id))); + + // current value + $dynamic = $this->searchConfig->getDynamicParameters(); + $filters = $dynamic->getFilters(); + if(isset($filters[$column->getFullQualifiedLabel()])) { + list(, $current) = $filters[$column->getFullQualifiedLabel()]; + $dynamic->removeFilter($column); + } else { + $current = ''; + } + + // Add current request params + $params = $dynamic->getURLParameters(); + foreach($params as $key => $val) { + $form->addHidden($key, $val); + } + + // add input field + $key = $column->getFullQualifiedLabel() . '*~'; + $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $current, '')); + $this->renderer->doc .= $form->getForm(); + } + $this->renderer->doc .= ''; + } + $this->renderer->doc .= ''; + + } + + /** + * Display the actual table data + */ + protected function renderRows() { + $this->renderer->tabletbody_open(); + foreach($this->result as $rownum => $row) { + $this->renderer->tablerow_open(); + + // row number column + if($this->data['rownumbers']) { + $this->renderer->tablecell_open(); + $this->renderer->doc .= $rownum + 1; + $this->renderer->tablecell_close(); + } + + /** @var Value $value */ + foreach($row as $colnum => $value) { + $this->renderer->tablecell_open(); + $value->render($this->renderer, $this->mode); + $this->renderer->tablecell_close(); + + // summarize + if($this->data['summarize'] && is_numeric($value->getValue())) { + if(!isset($this->sums[$colnum])) { + $this->sums[$colnum] = 0; + } + $this->sums[$colnum] += $value->getValue(); + } + } + $this->renderer->tablerow_close(); + } + $this->renderer->tabletbody_close(); + } + + /** + * Renders an information row for when no results were found + */ + protected function nullRow() { + $this->renderer->tablerow_open(); + $this->renderer->tablecell_open(count($this->data['cols']) + $this->data['rownumbers'], 'center'); + $this->renderer->cdata($this->helper->getLang('none')); + $this->renderer->tablecell_close(); + $this->renderer->tablerow_close(); + } + + /** + * Add sums if wanted + */ + protected function summarize() { + if($this->data['summarize']) return; + + $this->renderer->tablerow_open(); + $len = count($this->data['cols']); + + if($this->data['rownumbers']) { + $this->renderer->tablecell_open(); + $this->renderer->tablecell_close(); + } + + for($i = 0; $i < $len; $i++) { + $this->renderer->tablecell_open(1, $this->data['align'][$i]); + if(!empty($sums[$i])) { + $this->renderer->cdata('∑ ' . $sums[$i]); + } else { + if($this->mode == 'xhtml') { + $this->renderer->doc .= ' '; + } + } + $this->renderer->tablecell_close(); + } + $this->renderer->tablerow_close(); + } + + /** + * Adds pagin controls to the table + */ + protected function addLimitControls() { + if(empty($this->data['limit'])) return; + if($this->mode != 'xhtml') ; + + $this->renderer->tablerow_open(); + $this->renderer->tableheader_open((count($this->data['cols']) + ($this->data['rownumbers'] ? 1 : 0))); + $offset = $this->data['offset']; + + // prev link + if($offset) { + $prev = $offset - $this->data['limit']; + if($prev < 0) { + $prev = 0; + } + + $dynamic = $this->searchConfig->getDynamicParameters(); + $dynamic->setOffset($prev); + $link = wl($this->id, $dynamic->getURLParameters()); + $this->renderer->doc .= ''; + } + + // next link + if($this->resultCount > $offset + $this->data['limit']) { + $next = $offset + $this->data['limit']; + $dynamic = $this->searchConfig->getDynamicParameters(); + $dynamic->setOffset($next); + $link = wl($this->id, $dynamic->getURLParameters()); + $this->renderer->doc .= ''; + } + + $this->renderer->tableheader_close(); + $this->renderer->tablerow_close(); + } +} diff --git a/meta/Search.php b/meta/Search.php index 1873451..bbbaf31 100644 --- a/meta/Search.php +++ b/meta/Search.php @@ -96,7 +96,16 @@ class Search { $col = $this->findColumn($colname); if(!$col) return; //FIXME do we really want to ignore missing columns? - $this->sortby[] = array($col, $asc); + $this->sortby[$col->getFullQualifiedLabel()] = array($col, $asc); + } + + /** + * Returns all set sort columns + * + * @return array + */ + public function getSorts() { + return $this->sortby; } /** diff --git a/meta/SearchConfig.php b/meta/SearchConfig.php index b5ad80b..38dde8c 100644 --- a/meta/SearchConfig.php +++ b/meta/SearchConfig.php @@ -45,11 +45,11 @@ class SearchConfig extends Search { $this->addSort($sort[0], $sort[1] === 'ASC'); } - if (!empty($config['limit'])) { + if(!empty($config['limit'])) { $this->setLimit($config['limit']); } - if (!empty($config['offset'])) { + if(!empty($config['offset'])) { $this->setLimit($config['offset']); } } @@ -66,24 +66,10 @@ class SearchConfig extends Search { } /** - * Access the current config. - * - * When no key is given the whole configuration is returned. With a key only - * that key's value is returned. Returns NULL on a non-existing key - * - * @param string $key - * @return mixed + * @return array the current config */ - public function getConf($key='') { - if($key) { - if(isset($this->config[$key])) { - return $this->config[$key]; - } else { - return null; - } - } else { - return $this->config; - } + public function getConf() { + return $this->config; } } diff --git a/meta/SearchConfigParameters.php b/meta/SearchConfigParameters.php index ffc87da..34a4f79 100644 --- a/meta/SearchConfigParameters.php +++ b/meta/SearchConfigParameters.php @@ -102,6 +102,9 @@ class SearchConfigParameters { /** * Adds another filter * + * When there is a filter for that column already, the new filter overwrites it. Setting a + * blank value is the same as calling @see removeFilter() + * * @param string|Column $column * @param string $comp the comparator * @param string $value the value to compare against @@ -110,7 +113,11 @@ class SearchConfigParameters { $column = $this->resolveColumn($column); if(!$column) return; - $this->filters[$column] = array($comp, $value); + if(trim($value) === '') { + $this->removeFilter($column); + } else { + $this->filters[$column] = array($comp, $value); + } } /** @@ -131,6 +138,13 @@ class SearchConfigParameters { $this->filters = array(); } + /** + * @return array the current filters + */ + public function getFilters() { + return $this->filters; + } + /** * Get the current parameters in a form that can be used to create URLs */ diff --git a/syntax/table.php b/syntax/table.php index bc3a702..87ae293 100644 --- a/syntax/table.php +++ b/syntax/table.php @@ -6,12 +6,13 @@ * @author Andreas Gohr, Michael Große */ -// must be run within Dokuwiki -use plugin\struct\meta\Column; + +use plugin\struct\meta\AggregationTable; use plugin\struct\meta\ConfigParser; use plugin\struct\meta\SearchConfig; use plugin\struct\meta\StructException; +// must be run within Dokuwiki if (!defined('DOKU_INC')) die(); class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin { @@ -68,8 +69,6 @@ class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin { } } - protected $sums = array(); - /** * Render xhtml output or metadata * @@ -80,379 +79,23 @@ class syntax_plugin_struct_table extends DokuWiki_Syntax_Plugin { */ public function render($mode, Doku_Renderer $renderer, $data) { if(!$data) return false; + global $ID; if($mode == 'metadata') { /** @var Doku_Renderer_metadata $renderer */ $renderer->meta['plugin']['struct']['hasaggregation'] = true; } - //reset counters - $this->sums = array(); - try { $search = new SearchConfig($data); - $data = $search->getConf(); - $rows = $search->execute(); - $cnt = $search->getCount(); - $cols = $search->getColumns(); - - if ($cnt === 0) { - $this->nullList($data, $mode, $renderer, $cols); - return true; - } - - $this->renderPreTable($mode, $renderer, $data, $cols); - $this->renderRows($mode, $renderer, $data, $rows); - $this->renderPostTable($mode, $renderer, $data, $cnt); + $table = new AggregationTable($ID, $mode, $renderer, $search); + $table->render(); } catch (StructException $e) { msg($e->getMessage(), -1, $e->getLine(), $e->getFile()); } return true; } - - /** - * create the pretext to the actual table rows - * - * @param string $mode - * @param Doku_Renderer $renderer - * @param array $data the configuration data - * @param Column[] $cols - */ - protected function renderPreTable($mode, Doku_Renderer $renderer, $data, $cols) { - $this->startScope($mode, $renderer, md5(serialize($data))); - $this->showActiveFilters($mode, $renderer); - $this->startTable($mode, $renderer); - $renderer->tablethead_open(); - $this->buildColumnHeaders($mode, $renderer, $data, $cols); - $this->addDynamicFilters($mode, $renderer, $data); - $renderer->tablethead_close(); - } - - /** - * @param string $mode current render mode - * @param Doku_Renderer $renderer - * @param array $data - * @param int $rowcnt - * @return string - */ - private function renderPostTable($mode, Doku_Renderer $renderer, $data, $rowcnt) { - $this->summarize($mode, $renderer, $data, $this->sums); - $this->addLimitControls($mode, $renderer, $data, $rowcnt); - $this->finishTableAndScope($mode, $renderer); - } - - /** - * if limit was set, add control - * - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param array $data the configuration of the table/search - * @param $rowcnt - */ - protected function addLimitControls($mode, Doku_Renderer $renderer, $data, $rowcnt) { - global $ID; - - if($data['limit']) { - $renderer->tablerow_open(); - $renderer->tableheader_open((count($data['cols']) + ($data['rownumbers'] ? 1 : 0))); - $offset = (int) $_REQUEST['dataofs']; - - // keep url params - $params = array(); - if (!empty($data['current_params']['dataflt'])) {$params['dataflt'] = $data['current_params']['dataflt'];} - if (!empty($data['current_params']['datasrt'])) {$params['datasrt'] = $data['current_params']['datasrt'];} - - if($offset) { - $prev = $offset - $data['limit']; - if($prev < 0) { - $prev = 0; - } - $params['dataofs'] = $prev; - $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('prev')); - } - - if($rowcnt > $offset + $data['limit']) { - $next = $offset + $data['limit']; - $params['dataofs'] = $next; - $renderer->internallink($ID . '?' . http_build_query($params), $this->getLang('next')); - } - $renderer->tableheader_close(); - $renderer->tablerow_close(); - } - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - */ - protected function showActiveFilters($mode, Doku_Renderer $renderer) { - global $ID; - - if($mode == 'xhtml' && !empty($data['current_params']['dataflt'])) { - $filters = $data['current_params']['dataflt']; - /** @var helper_plugin_struct_config $confHelper */ - $confHelper = $this->loadHelper('struct_config'); - $fltrs = array(); - foreach($filters as $colcomp => $filter) { - $filter = $confHelper->parseFilterLine('', $colcomp.$filter); - if(strpos($filter[1], '~') !== false) { - if(strpos($filter[1], '!~') !== false) { - $comparator_value = '!~' . str_replace('%', '*', $filter[2]); - } else { - $comparator_value = '~' . str_replace('%', '', $filter[2]); - } - $fltrs[] = $filter[0] . $comparator_value; - } else { - $fltrs[] = $filter[0] . $filter[1] . $filter[2]; - } - } - - $renderer->doc .= '
'; - $renderer->doc .= '

' . sprintf($this->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '

'; - $renderer->doc .= '
'; - $renderer->internallink($ID, $this->getLang('tableresetfilter')); - $renderer->doc .= '
'; - $renderer->doc .= '
'; - } - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param array $data the configuration of the table/search - */ - protected function addDynamicFilters($mode, Doku_Renderer $renderer, $data) { - if ($mode != 'xhtml') return; - - global $conf, $ID; - - $cur_params = $data['current_params']; - $html = ''; - if($data['dynfilters']) { - $html .= ''; - - if($data['rownumbers']) { - $html .= ''; - } - - foreach($data['headers'] as $num => $head) { - $html .= ''; - $form = new Doku_Form(array('method' => 'GET',)); - $form->_hidden = array(); - if(!$conf['userewrite']) { - $form->addHidden('id', $ID); - } - - $key = $data['cols'][$num] . '*~'; - $val = isset($cur_params['dataflt'][$key]) ? $cur_params['dataflt'][$key] : ''; - - // Add current request params - if (!empty($cur_params['datasrt'])) { - $form->addHidden('datasrt', $cur_params['datasrt']); - } - if (!empty($cur_params['dataofs'])) { - $form->addHidden('dataofs', $cur_params['dataofs']); - } - if (!empty($cur_params['dataflt'])) foreach($cur_params['dataflt'] as $c_key => $c_val) { - if($c_val !== '' && $c_key !== $key) { - $form->addHidden('dataflt[' . $c_key . ']', $c_val); - } - } - - $form->addElement(form_makeField('text', 'dataflt[' . $key . ']', $val, '')); - $html .= $form->getForm(); - $html .= ''; - } - $html .= ''; - $renderer->doc .= $html; - } - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - */ - private function startTable($mode, Doku_Renderer $renderer) { - $renderer->table_open(); - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param array $data the configuration of the table/search - * @param Column[] $cols - */ - protected function buildColumnHeaders($mode, Doku_Renderer $renderer, $data, $cols) { - global $ID; - - $renderer->tablerow_open(); - - if($data['rownumbers']) { - $renderer->tableheader_open(); - $renderer->cdata('#'); - $renderer->tableheader_close(); - } - - foreach($data['headers'] as $num => $head) { - $ckey = $data['cols'][$num]; - if(blank($head)) { - if(isset($cols[$num]) && is_a($cols[$num], 'plugin\struct\meta\PageColumn')) { - $head = $this->getLang('pagelabel'); - }else if(isset($cols[$num]) && is_a($cols[$num], 'plugin\struct\meta\Column')) { - $head = $cols[$num]->getTranslatedLabel(); - } else { - $head = 'column '.$num; // this should never happen - } - } - - $width = ''; - if(isset($data['widths'][$num]) AND $data['widths'][$num] != '-') { - $width = ' style="width: ' . $data['widths'][$num] . ';"'; - } - if ($mode == 'xhmtl') { - $renderer->doc .= ''; - } else { - $renderer->tableheader_open(); - } - - // output header - if ($mode == 'xhtml') { - $sort = ''; - if(isset($data['sort']) && $ckey == $data['sort'][0]) { - if($data['sort'][1] == 'ASC') { - $sort = 'sort-down'; - $ckey = '^' . $ckey; - } else { - $sort = 'sort-up'; - } - } - - $params = $data['current_params']; - $params['datasrt'] = $ckey; - $link = wl($ID, $params); - $renderer->doc .= ''.hsc($head).''; - } else { - $renderer->cdata($head); - } - - $renderer->tableheader_close(); - } - $renderer->tablerow_close(); - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param string $hash hash to identify the table and group images in gallery - */ - protected function startScope($mode, \Doku_Renderer $renderer, $hash) { - if ($mode == 'xhtml') { - $renderer->doc .= "
"; - $renderer->info['struct_table_hash'] = $hash; - } - } - - /** - * if summarize was set, add sums - * - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param array $data the configuration of the table/search - * @param array $sums the summarized output of the numerical fields - */ - private function summarize($mode, \Doku_Renderer $renderer, $data, $sums) { - if($data['summarize']) { - $renderer->tablerow_open(); - $len = count($data['cols']); - - if($data['rownumbers']) { - $renderer->tablecell_open(); - $renderer->tablecell_close(); - } - - for($i = 0; $i < $len; $i++) { - $renderer->tablecell_open(1, $data['align'][$i]); - if(!empty($sums[$i])) { - $renderer->cdata('∑ ' . $sums[$i]); - } else { - if ($mode == 'xhtml') { - $renderer->doc .= ' '; - } - } - $renderer->tablecell_close(); - } - $renderer->tablerow_close(); - } - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * - */ - private function finishTableAndScope($mode, Doku_Renderer $renderer) { - $renderer->table_close(); - if ($mode == 'xhtml') { - $renderer->doc .= '
'; - if(isset($renderer->info['struct_table_hash'])) { - unset($renderer->info['struct_table_hash']); - } - } - } - - /** - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param array $data the configuration of the table/search - * @param $rows - * - */ - private function renderRows($mode, Doku_Renderer $renderer, $data, $rows) { - $renderer->tabletbody_open(); - foreach($rows as $rownum => $row) { - $renderer->tablerow_open(); - - if($data['rownumbers']) { - $renderer->tablecell_open(); - $renderer->doc .= $rownum + 1; - $renderer->tablecell_close(); - } - - /** @var plugin\struct\meta\Value $value */ - foreach($row as $colnum => $value) { - $renderer->tablecell_open(); - $value->render($renderer, $mode); - $renderer->tablecell_close(); - - // summarize - if($data['summarize'] && is_numeric($value->getValue())) { - if(!isset($this->sums[$colnum])) { - $this->sums[$colnum] = 0; - } - $this->sums[$colnum] += $value->getValue(); - } - } - $renderer->tablerow_close(); - } - $renderer->tabletbody_close(); - } - - /** - * @param array $data the configuration of the table/search - * @param string $mode the mode of the renderer - * @param Doku_Renderer $renderer the renderer - * @param Column[] $cols - */ - private function nullList($data, $mode, Doku_Renderer $renderer, $cols) { - $this->renderPreTable($mode, $renderer, $data, $cols); - $renderer->tablerow_open(); - $renderer->tablecell_open(count($data['cols']) + $data['rownumbers'], 'center'); - $renderer->cdata($this->getLang('none')); - $renderer->tablecell_close(); - $renderer->tablerow_close(); - $renderer->table_close(); - } } // vim:ts=4:sw=4:et: