diff --git a/_test/helper.test.php b/_test/helper.test.php index c85c59e..ad218fc 100644 --- a/_test/helper.test.php +++ b/_test/helper.test.php @@ -202,14 +202,15 @@ function testReplacePlaceholdersInSQL() { $this->assertEquals('en', $data['sql']); } - protected function createColumnEntry($name, $multi, $key, $origkey, $title, $type) { + protected function createColumnEntry($name, $multi, $key, $origkey, $title, $type, $datatype = '') { return array( 'colname' => $name, 'multi' => $multi, 'key' => $key, 'origkey' => $origkey, 'title' => $title, - 'type' => $type + 'type' => $type, + 'datatype' => $datatype ); } diff --git a/_test/syntax_plugin_data_entry.test.php b/_test/syntax_plugin_data_entry.test.php index c657cdb..bae1230 100644 --- a/_test/syntax_plugin_data_entry.test.php +++ b/_test/syntax_plugin_data_entry.test.php @@ -309,14 +309,15 @@ function testHandleEmpty() { $this->assertEquals($cols, $result['cols'], 'Cols array corrupted'); } - protected function createColumnEntry($name, $multi, $key, $origkey, $title, $type) { + protected function createColumnEntry($name, $multi, $key, $origkey, $title, $type, $datatype = '') { return array( 'colname' => $name, 'multi' => $multi, 'key' => $key, 'origkey' => $origkey, 'title' => $title, - 'type' => $type + 'type' => $type, + 'datatype' => $datatype ); } diff --git a/_test/syntax_plugin_data_table.test.php b/_test/syntax_plugin_data_table.test.php index ddef008..61be636 100644 --- a/_test/syntax_plugin_data_table.test.php +++ b/_test/syntax_plugin_data_table.test.php @@ -13,7 +13,7 @@ class syntax_plugin_data_table_test extends DokuWikiTest { . 'headers : Details, "Assigned Employees \#no", stuff outside quotes """Deadline, ", Personal website, $$$'."\n" . "max : 10\n" . "filter : type=web development\n" - . "sort : ^volume\n" + . "sort : ^(num)volume,%pageid%\n" . "dynfilters: 1\n" . "summarize : 1\n" . "align : c\n" @@ -30,10 +30,10 @@ function testHandle() { $data = array( 'classes' => 'employees', 'limit' => 10, - 'dynfilters' => 1, - 'summarize' => 1, - 'rownumbers' => 1, - 'sepbyheaders' => '', + 'dynfilters' => true, + 'summarize' => true, + 'rownumbers' => true, + 'sepbyheaders' => false, 'headers' => array( '0' => 'Details', '1' => 'Assigned Employees #no', @@ -65,6 +65,7 @@ function testHandle() { 'origkey' => '%pageid%', 'title' => 'Title', 'type' => 'page', + 'datatype' => '' ), 'employee' => array( 'colname' => 'employees', @@ -72,7 +73,8 @@ function testHandle() { 'key' => 'employee', 'origkey' => 'employee', 'title' => 'employee', - 'type' => '' + 'type' => '', + 'datatype' => '' ), 'deadline' => array( 'colname' => 'deadline_dt', @@ -80,7 +82,8 @@ function testHandle() { 'key' => 'deadline', 'origkey' => 'deadline', 'title' => 'deadline', - 'type' => 'dt' + 'type' => 'dt', + 'datatype' => '' ), 'website' => array( 'colname' => 'website_url', @@ -88,7 +91,8 @@ function testHandle() { 'key' => 'website', 'origkey' => 'website', 'title' => 'website', - 'type' => 'url' + 'type' => 'url', + 'datatype' => '' ), 'volume' => array( 'colname' => 'volume', @@ -96,13 +100,14 @@ function testHandle() { 'key' => 'volume', 'origkey' => 'volume', 'title' => 'volume', - 'type' => '' + 'type' => '', + 'datatype' => '' ), ), 'sort' => array( - '0' => 'volume', - '1' => 'DESC' + 'volume' => array('volume', 'DESC', 'numeric'), + '%pageid%' => array('%pageid%', 'ASC', '') ), 'align' => array( '0' => 'center' @@ -120,7 +125,7 @@ function testHandle() { LEFT JOIN data AS T1 ON T1.pid = W1.pid AND T1.key = 'employee' LEFT JOIN data AS T2 ON T2.pid = W1.pid AND T2.key = 'deadline' LEFT JOIN data AS T3 ON T3.pid = W1.pid AND T3.key = 'website' LEFT JOIN data AS T4 ON T4.pid = W1.pid AND T4.key = 'volume' LEFT JOIN pages ON W1.pid=pages.pid GROUP BY W1.pid - ORDER BY T4.value DESC LIMIT 11", + ORDER BY CAST(T4.value AS NUMERIC) DESC, pages.page ASC LIMIT 11", 'cur_param' => array() ); diff --git a/helper.php b/helper.php index 56e8693..d105cad 100644 --- a/helper.php +++ b/helper.php @@ -364,15 +364,16 @@ function _formatData($column, $value, Doku_Renderer_xhtml $R) { * @param string $col column name * @return array with key, type, ismulti, title, opt */ - function _column($col) { - preg_match('/^([^_]*)(?:_(.*))?((? $col, - 'multi' => ($matches[3] === 's'), + 'multi' => ($matches[4] === 's'), 'key' => utf8_strtolower($matches[1]), 'origkey' => $matches[1], //similar to key, but stores upper case 'title' => $matches[1], - 'type' => utf8_strtolower($matches[2]) + 'type' => utf8_strtolower($matches[3]), + 'datatype'=> $matches[2] == '(num)' ? 'numeric' : '' ); // fix title for special columns diff --git a/syntax/table.php b/syntax/table.php index c0a2b39..c1eeadc 100644 --- a/syntax/table.php +++ b/syntax/table.php @@ -152,12 +152,10 @@ function handle($match, $state, $pos, Doku_Handler $handler) { break; case 'order': case 'sort': - $column = $this->dthlp->_column($line[1]); - $sort = $column['key']; - if(substr($sort, 0, 1) == '^') { - $data['sort'] = array(substr($sort, 1), 'DESC'); - } else { - $data['sort'] = array($sort, 'ASC'); + $cols = explode(',', $line[1]); + foreach($cols as $col) { + $param = $this->parseSortParam($col); + $data['sort'][$param[0]] = $param; } break; case 'where': @@ -408,13 +406,17 @@ function preList($clist, $data) { $text .= ''; // add sort arrow - if(isset($data['sort']) && $ckey == $data['sort'][0]) { - if($data['sort'][1] == 'ASC') { + if(isset($data['sort']) && isset($data['sort'][$ckey])) { + $sortterm = $data['sort'][$ckey]; + if($sortterm[1] == 'ASC'){ $text .= ' '; $ckey = '^' . $ckey; } else { $text .= ' '; } + if($sortterm[2] == 'numeric') { + $ckey = $ckey . '(num)'; + } } // Clickable header for dynamic sorting @@ -608,26 +610,35 @@ function _buildSQL(&$data) { // prepare sorting if(isset($data['sort'])) { - $col = $data['sort'][0]; - - if($col == '%pageid%') { - $order = 'ORDER BY pages.page ' . $data['sort'][1]; - } elseif($col == '%class%') { - $order = 'ORDER BY pages.class ' . $data['sort'][1]; - } elseif($col == '%title%') { - $order = 'ORDER BY pages.title ' . $data['sort'][1]; - } elseif($col == '%lastmod%') { - $order = 'ORDER BY pages.lastmod ' . $data['sort'][1]; - } else { - // sort by hidden column? - if(!$tables[$col]) { - $tables[$col] = 'T' . (++$cnt); - $from .= ' LEFT JOIN data AS ' . $tables[$col] . ' ON ' . $tables[$col] . '.pid = W1.pid'; - $from .= ' AND ' . $tables[$col] . ".key = " . $sqlite->quote_string($col); - } + $orderingterms = array(); + foreach($data['sort'] AS $term) { + @list($col, $direction, $type) = $term; + + if($col == '%pageid%') { + $sortcolumn = 'pages.page'; + } elseif($col == '%class%') { + $sortcolumn = 'pages.class'; + } elseif($col == '%title%') { + $sortcolumn = 'pages.title'; + } elseif($col == '%lastmod%') { + $sortcolumn = 'pages.lastmod'; + } else { + // sort by hidden column? + if(!$tables[$col]) { + $tables[$col] = 'T' . (++$cnt); + $from .= ' LEFT JOIN data AS ' . $tables[$col] . ' ON ' . $tables[$col] . '.pid = W1.pid'; + $from .= ' AND ' . $tables[$col] . ".key = " . $sqlite->quote_string($col); + } - $order = 'ORDER BY ' . $tables[$col] . '.value ' . $data['sort'][1]; + $sortcolumn = $tables[$col] . '.value'; + } + if($type == 'numeric') { + $sortcolumn = 'CAST(' . $sortcolumn . ' AS NUMERIC)'; + } + $orderingterms[] = $sortcolumn . ' ' . $direction; } + + $order = 'ORDER BY ' . implode(', ', $orderingterms); } else { $order = 'ORDER BY 1 ASC'; } @@ -651,7 +662,7 @@ function _buildSQL(&$data) { $where2 .= " " . $filter['logic'] . " pages.title " . $filter['compare'] . " '" . $filter['value'] . "'" . $closecompare; } elseif($col == '%lastmod%') { # parse value to int? - $filter['value'] = (int) strtotime($filter['value']); + $filter['value'] = (int)strtotime($filter['value']); $where2 .= " " . $filter['logic'] . " pages.lastmod " . $filter['compare'] . " " . $filter['value'] . $closecompare; } else { // filter by hidden column? @@ -700,11 +711,12 @@ function _buildSQL(&$data) { function updateSQLwithQuery(&$data) { if($this->hasRequestFilter()) { if(isset($_REQUEST['datasrt'])) { - if($_REQUEST['datasrt'][0] == '^') { - $data['sort'] = array(substr($_REQUEST['datasrt'], 1), 'DESC'); - } else { - $data['sort'] = array($_REQUEST['datasrt'], 'ASC'); + @list($sort, $direction, $type) = $this->parseSortParam($_REQUEST['datasrt']); + //use stored numeric properties + if(isset($data['sort']) && isset($data['sort'][$sort]) && $data['sort'][$sort][2] == 'numeric') { + $type = 'numeric'; } + $data['sort'] = array($sort => array($sort, $direction, $type)); } // add request filters @@ -786,5 +798,29 @@ protected function parseValues($line) { } return $values; } + + /** + * Parse a sort option like: [^]column[(num)] + * + * @param string $col column to sort + * @return array(string $sort, string $direction, string $type) + */ + private function parseSortParam($col) { + $column = $this->dthlp->_column($col); + $sort = $column['key']; + $type = ''; + $direction = 'ASC'; + + $pos = strpos($sort, '(num)'); + if($pos !== false) { + $sort = str_replace('(num)', '', $sort); + $type = 'numeric'; + } + if(substr($sort, 0, 1) == '^') { + $sort = substr($sort, 1); + $direction = 'DESC'; + } + return array($sort, $direction, $type); + } }