diff --git a/js/columndelete.js b/js/columndelete.js new file mode 100644 index 0000000000..8282e9bb31 --- /dev/null +++ b/js/columndelete.js @@ -0,0 +1,68 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @fileoverview Implements the shiftkey + click remove column + * from order by clause funcationality + * @name columndelete + * + * @requires jQuery + */ + +function captureURL(url) +{ + var URL = {}; + url = '' + url; + // Exclude the url part till HTTP + url = url.substr(url.search("sql.php"), url.length); + // The url part between ORDER BY and &session_max_rows needs to be replaced. + URL['head'] = url.substr(0, url.indexOf('ORDER+BY') + 9); + URL['tail'] = url.substr(url.indexOf("&session_max_rows"), url.length); + return URL; +} + +/** + * This function is for navigating to the generated URL + * + * @param object target HTMLAnchor element + * @param object parent HTMLDom Object + */ + +function redirect(target, parent) +{ + var URL = captureURL(target); + var begin = target.indexOf('ORDER+BY') + 8; + var end = target.indexOf('&session_max_rows'); + // get the names of the columns involved + var between_part = target.substr(begin, end-begin); + var columns = between_part.split('%2C+'); + // If the given column is not part of the order clause exit from this function + var index = parent.find('small').length ? parent.find('small').text() : ''; + if (index == ''){ + return; + } + // Remove the current clicked column + columns.splice(index-1, 1); + // If all the columns have been removed dont submit a query with nothing + // After order by clause. + if (columns.length == 0){ + var head = URL['head']; + head = head.slice(0,head.indexOf('ORDER+BY')); + URL['head'] = head; + } + var middle_part = columns.join('%2C+'); + url = URL['head'] + middle_part + URL['tail']; + window.location.replace(url); +} + + +AJAX.registerOnload('keyhandler.js', function () { + $("th.draggable.column_heading.pointer.marker a").live('click', function (event) { + if (event.shiftKey) { + event.preventDefault(); + redirect($(this).attr("href"), $(this).parent()); + } + }); +}); + +AJAX.registerTeardown('keyhandler.js', function () { + $("th.draggable.column_heading.pointer.marker a").die('click'); +}); diff --git a/js/makegrid.js b/js/makegrid.js index 41f22d5981..6c21698654 100644 --- a/js/makegrid.js +++ b/js/makegrid.js @@ -437,6 +437,10 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi if (g.showSortHint && g.sortHint) { text += text.length > 0 ? '
' : ''; text += g.sortHint; + } + if (g.showRemColHint && g.strRemColHint) { + text += text.length > 0 ? '
' : ''; + text += g.strRemColHint; } if (g.showMarkHint && g.markHint && !g.showSortHint // we do not show mark hint, when sort hint is shown @@ -1784,6 +1788,7 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi // assign the hints g.sortHint = PMA_messages.strSortHint; + g.strRemColHint = PMA_messages.strRemColHint; g.markHint = PMA_messages.strColMarkHint; g.copyHint = PMA_messages.strColNameCopyHint; @@ -1837,12 +1842,14 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi $(t).find('th.draggable a') .mouseenter(function (e) { g.showSortHint = true; + g.showRemColHint = true; $(t).find("th.draggable").tooltip("option", { content: g.updateHint() }); }) .mouseleave(function (e) { - g.showSortHint = false; + g.showSortHint = false; + g.showRemColHint = false; $(t).find("th.draggable").tooltip("option", { content: g.updateHint() }); diff --git a/js/messages.php b/js/messages.php index 502a64abf9..da01cbce3f 100644 --- a/js/messages.php +++ b/js/messages.php @@ -335,6 +335,7 @@ $js_messages['strCellEditHint'] = __('Press escape to cancel editing.'); $js_messages['strSaveCellWarning'] = __('You have edited some data and they have not been saved. Are you sure you want to leave this page before saving the data?'); $js_messages['strColOrderHint'] = __('Drag to reorder.'); $js_messages['strSortHint'] = __('Click to sort.'); +$js_messages['strRemColHint'] = __('Hold shift and click to remove column from order by clause.'); $js_messages['strColMarkHint'] = __('Click to mark/unmark.'); $js_messages['strColNameCopyHint'] = __('Double-click to copy column name.'); $js_messages['strColVisibHint'] = __( diff --git a/libraries/DisplayResults.class.php b/libraries/DisplayResults.class.php index 0412831894..bdad3cfdc1 100644 --- a/libraries/DisplayResults.class.php +++ b/libraries/DisplayResults.class.php @@ -1140,7 +1140,7 @@ class PMA_DisplayResults $sort_expression_nodirection, $i, $unsorted_sql_query, $session_max_rows, $direction, $comments, $sort_direction, $directionCondition, $col_visib, - $col_visib[$j], $condition_field + $col_visib[$j] ); $table_headers_html .= $sorted_header_html; @@ -1816,14 +1816,14 @@ class PMA_DisplayResults * Prepare parameters and html for sorted table header fields * * @param array $fields_meta set of field properties - * @param string $sort_expression sort expression - * @param string $sort_expression_nodirection sort expression without direction + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction * @param integer $column_index the index of the column * @param string $unsorted_sql_query the unsorted sql query * @param integer $session_max_rows maximum rows resulted by sql - * @param string $direction the display direction + * @param array $direction the display direction * @param string $comments comment for row - * @param string $sort_direction sort direction + * @param array $sort_direction sort direction * @param boolean $directionCondition display direction horizontal * or horizontalflipped * @param boolean $col_visib column is visible(false) @@ -1837,12 +1837,12 @@ class PMA_DisplayResults * @access private * * @see _getTableHeaders() - */ + */ private function _getOrderLinkAndSortedHeaderHtml( $fields_meta, $sort_expression, $sort_expression_nodirection, $column_index, $unsorted_sql_query, $session_max_rows, $direction, $comments, $sort_direction, $directionCondition, $col_visib, - $col_visib_j, $condition_field + $col_visib_j, $condition_field, $is_last_field ) { $sorted_header_html = ''; @@ -1860,50 +1860,14 @@ class PMA_DisplayResults ) . '.' : ''; - // Checks if the current column is used to sort the - // results - // the orgname member does not exist for all MySQL versions - // but if found, it's the one on which to sort $name_to_use_in_sort = $fields_meta->name; - $is_orgname = false; - if (isset($fields_meta->orgname) - && strlen($fields_meta->orgname) - ) { - $name_to_use_in_sort = $fields_meta->orgname; - $is_orgname = true; - } - // $name_to_use_in_sort might contain a space due to - // formatting of function expressions like "COUNT(name )" - // so we remove the space in this situation - $name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort); - - $is_in_sort = $this->_isInSorted( - $sort_expression, $sort_expression_nodirection, - $sort_tbl, $name_to_use_in_sort - ); - - // Check the field name for a bracket. - // If it contains one, it's probably a function column - // like 'COUNT(`field`)' - // It still might be a column name of a view. See bug #3383711 - // Check is_orgname. - if ((strpos($name_to_use_in_sort, '(') !== false) && ! $is_orgname) { - $sort_order = "\n" . 'ORDER BY ' . $name_to_use_in_sort . ' '; - } else { - $sort_order = "\n" . 'ORDER BY ' . $sort_tbl - . PMA_Util::backquote( - $name_to_use_in_sort - ) . ' '; - } - unset($name_to_use_in_sort); - unset($is_orgname); - - // Do define the sorting URL - - list($sort_order, $order_img) = $this->_getSortingUrlParams( - $is_in_sort, $sort_direction, $fields_meta, - $sort_order, $column_index + // Generates the orderby clause part of the query which is part + // of URL + list($sort_order, $order_img) = $this->makeUrl( + $sort_expression, $sort_expression_nodirection, + $sort_tbl, $name_to_use_in_sort, $sort_direction, $fields_meta, + $column_index ); if (preg_match( @@ -1931,23 +1895,124 @@ class PMA_DisplayResults $fields_meta, $order_url ); - if ($directionCondition) { - $sorted_header_html .= $this->_getDraggableClassForSortableColumns( - $col_visib, $col_visib_j, $condition_field, $direction, - $fields_meta, $order_link, $comments - ); - } + $sorted_header_html .= $this->_getDraggableClassForSortableColumns( + $col_visib, $col_visib_j, $condition_field, $direction, + $fields_meta, $order_link, $comments + ); return array($order_link, $sorted_header_html); } // end of the '_getOrderLinkAndSortedHeaderHtml()' function + /** + * Prepare parameters and html for sorted table header fields + * + * @param array $fields_meta set of field properties + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction + * @param array $sort_direction sort direction + * @param string $name_to_use_in_sort The current column under consideration + * @param string $sort_tbl The name of the table to which the + * current column belongs to + * @param integer $column_index The index number to the current column + * @return array 2 element array - $order_link, $sorted_header_html + * + * @access private + * + * @see _getTableHeaders() + */ + private function makeURL( + $sort_expression, $sort_expression_nodirection, + $sort_tbl, $name_to_use_in_sort, $sort_direction, $fields_meta, + $column_index + ){ + $sort_order = ""; + // Check if the current column is in the order by clause + $is_in_sort = $this->_isInSorted( + $sort_expression, $sort_expression_nodirection, + $sort_tbl, $name_to_use_in_sort + ); + $current_name = $name_to_use_in_sort; + if ($sort_expression_nodirection[0] == '' || !$is_in_sort){ + $special_index = $sort_expression_nodirection[0] == '' ? 0 : count($sort_expression_nodirection); + $sort_expression_nodirection[$special_index] = + PMA_Util::backquote( + $current_name + ); + $sort_direction[$special_index] = (preg_match( + '@time|date@i', + $fields_meta->type + )) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR; + + } + $sort_expression_nodirection = array_filter($sort_expression_nodirection); + foreach ($sort_expression_nodirection as $index=>$expression){ + // check if this is the first clause, if it is then we have to add "order by" + $is_first_clause = ($index == 0); + $name_to_use_in_sort = $expression; + $sort_tbl_new = $sort_tbl; + // Test to detect if the column name is a standard name + // Standard name has the table name prefixed to the column name + $is_standard_name = false; + if (strpos($name_to_use_in_sort, '.') !== false){ + $matches = explode('.', $name_to_use_in_sort); + // Matches[0] has the table name + // Matches[1] has the column name + $name_to_use_in_sort = $matches[1]; + $sort_tbl_new = $matches[0]; + $is_standard_name = true; + } + + + // $name_to_use_in_sort might contain a space due to + // formatting of function expressions like "COUNT(name )" + // so we remove the space in this situation + $name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort); + $name_to_use_in_sort = str_replace('`','',$name_to_use_in_sort); + + // If this the first column name in the order by clause add + // order by clause to the column name + $query_head = $is_first_clause ? "\nORDER BY " : ""; + $tbl = $is_standard_name ? $sort_tbl_new : $sort_tbl; + // Again a check to see if the given column is a aggregate column + if (strpos($name_to_use_in_sort, '(') !== false) { + $sort_order .= $query_head . $name_to_use_in_sort . ' ' ; + } else { + $sort_order .= $query_head . $sort_tbl_new . "." + . PMA_Util::backquote( + $name_to_use_in_sort + ) . ' ' ; + } + + // For a special case where the code generates two dots between + // column name and table name. + $sort_order = preg_replace("/\.\./", ".", $sort_order); + // Incase the current column name is in the order by clause + // We need to generate the arrow button and related html + if($current_name == $name_to_use_in_sort && $is_in_sort){ + list($sort_order, $order_img) = $this->_getSortingUrlParams( + $is_in_sort, $sort_direction, $fields_meta, + $sort_order, $column_index, $sort_tbl_new, $index + ); + } + else{ + $sort_order .= strtoupper($sort_direction[$index]); + } + // Separte columns by a comma + $sort_order .= ", "; + unset($name_to_use_in_sort); + } + // remove the comma from the last column name in the newly + // constructed clause + $sort_order = substr($sort_order,0,strlen($sort_order)-2); + return array($sort_order, $order_img); + } /** * Check whether the column is sorted * - * @param string $sort_expression sort expression - * @param string $sort_expression_nodirection sort expression without direction + * @param array $sort_expression sort expression + * @param array $sort_expression_nodirection sort expression without direction * @param string $sort_tbl the table name * @param string $name_to_use_in_sort the sorting column name * @@ -1961,8 +2026,23 @@ class PMA_DisplayResults $sort_expression, $sort_expression_nodirection, $sort_tbl, $name_to_use_in_sort ) { - - if (empty($sort_expression)) { + + $index_in_expression = 0; + + foreach ($sort_expression_nodirection as $index => $clause) { + if (strpos($clause,'.') !== false) { + $fragments = explode('.', $clause); + $clause2 = $fragments[0] . "." . str_replace('`', ``, $fragments[1]); + } + else { + $clause2 = $sort_tbl . str_replace('`', ``, $clause); + } + if ($clause2 === $sort_tbl . $name_to_use_in_sort) { + $index_in_expression = $index; + break; + } + } + if (empty($sort_expression[$index_in_expression])) { $is_in_sort = false; } else { // Field name may be preceded by a space, or any number @@ -1975,13 +2055,13 @@ class PMA_DisplayResults // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p // (and try clicking on each column's header twice) if (! empty($sort_tbl) - && strpos($sort_expression_nodirection, $sort_tbl) === false - && strpos($sort_expression_nodirection, '(') === false + && strpos($sort_expression_nodirection[$index_in_expression], $sort_tbl) === false + && strpos($sort_expression_nodirection[$index_in_expression], '(') === false ) { $new_sort_expression_nodirection = $sort_tbl - . $sort_expression_nodirection; + . $sort_expression_nodirection[$index_in_expression]; } else { - $new_sort_expression_nodirection = $sort_expression_nodirection; + $new_sort_expression_nodirection = $sort_expression_nodirection[$index_in_expression]; } //Back quotes are removed in next comparison, so remove them from value @@ -1992,7 +2072,7 @@ class PMA_DisplayResults $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort; if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection) - || $sort_name == str_replace('`', '', $sort_expression_nodirection) + || $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression]) ) { $is_in_sort = true; } @@ -2007,9 +2087,9 @@ class PMA_DisplayResults * Get sort url paramaeters - sort order and order image * * @param boolean $is_in_sort the column sorted or not - * @param string $sort_direction the sort direction + * @param array $sort_direction the sort direction * @param array $fields_meta set of field properties - * @param string $sort_order the sorting order + * @param array $sort_order the sorting order * @param integer $column_index the index of the column * * @return array 2 element array - $sort_order, $order_img @@ -2019,26 +2099,13 @@ class PMA_DisplayResults * @see _getTableHeaders() */ private function _getSortingUrlParams( - $is_in_sort, $sort_direction, $fields_meta, $sort_order, $column_index - ) { - - if (! $is_in_sort) { - - // patch #455484 ("Smart" order) - $GLOBALS['cfg']['Order'] = strtoupper($GLOBALS['cfg']['Order']); - - if ($GLOBALS['cfg']['Order'] === self::SMART_SORT_ORDER) { - $sort_order .= (preg_match( - '@time|date@i', - $fields_meta->type - )) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR; - } else { - $sort_order .= $GLOBALS['cfg']['Order']; - } - $order_img = ''; - - } elseif ($sort_direction == self::DESCENDING_SORT_DIR) { + $is_in_sort, $sort_direction, $fields_meta, $sort_order, $column_index, $sort_tbl, $index + ) { + $index2 = $index + 1; + if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) { + + $sort_number = "".$index2.""; $sort_order .= ' ASC'; $order_img = ' ' . PMA_Util::getImage( 's_desc.png', __('Descending'), @@ -2048,20 +2115,22 @@ class PMA_DisplayResults $order_img .= ' ' . PMA_Util::getImage( 's_asc.png', __('Ascending'), array('class' => "soimg$column_index hide", 'title' => '') - ); + ) . $sort_number; } else { + $sort_number = "".$index2.""; $sort_order .= ' DESC'; $order_img = ' ' . PMA_Util::getImage( 's_asc.png', __('Ascending'), array('class' => "soimg$column_index", 'title' => '') ); - $order_img .= ' ' . PMA_Util::getImage( + $order_img .= ' ' . PMA_Util::getImage( 's_desc.png', __('Descending'), array('class' => "soimg$column_index hide", 'title' => '') - ); + ) . $sort_number; + } return array($sort_order, $order_img); @@ -2088,8 +2157,7 @@ class PMA_DisplayResults $order_img, $col_index, $direction, $fields_meta, $order_url ) { - $order_link_params = array(); - + $order_link_params = array(); if (isset($order_img) && ($order_img != '')) { if (strstr($order_img, 'asc')) { $order_link_params['onmouseover'] = "$('.soimg$col_index').toggle()"; @@ -4567,16 +4635,16 @@ class PMA_DisplayResults // 1.3 Find the sort expression // we need $sort_expression and $sort_expression_nodirection // even if there are many table references - list($sort_expression, $sort_expression_nodirection, $sort_direction) + list($sort_expression, $sort_expression_nodirection, $sort_direction,$number_of_columns) = $this->_getSortParams($analyzed_sql[0]['order_by_clause']); // 1.4 Prepares display of first and last value of the sorted column - - $sorted_column_message = $this->_getSortedColumnMessage( - $dt_result, $sort_expression_nodirection - ); - + for ( $i = 0; $i < $number_of_columns; $i++ ){ + $sorted_column_message = $this->_getSortedColumnMessage( + $dt_result, $sort_expression_nodirection[$i] + ); + } // 2. ----- Prepare to display the top of the page ----- @@ -4759,35 +4827,39 @@ class PMA_DisplayResults * @see getTable() */ private function _getSortParams($order_by_clause) - { + { - if (! empty($order_by_clause)) { + if (! empty($order_by_clause)) { + // Each order by clause is assumed to be delimited by a comma + // A typical order by clause would be order by column1 asc, column2 desc + // The following line counts the number of columns in order by clause + $matches = explode(',', $order_by_clause); + // Iterate over each column in order by clause + foreach ($matches as $index=>$order_by_clause2) { - $sort_expression = trim( - str_replace(' ', ' ', $order_by_clause) - ); - /** - * Get rid of ASC|DESC - */ - preg_match( - '@(.*)([[:space:]]*(ASC|DESC))@si', $sort_expression, $matches - ); - - $sort_expression_nodirection = isset($matches[1]) - ? trim($matches[1]) - : $sort_expression; - - $sort_direction = isset($matches[2]) ? trim($matches[2]) : ''; - unset($matches); + $sort_expression[$index] = trim( + str_replace(' ', ' ', $order_by_clause2) + ); + /** + * Get rid of ASC|DESC + */ + preg_match( + '@(.*)([[:space:]]*(ASC|DESC))@si', $sort_expression[$index], $matches + ); + $sort_expression_nodirection[$index] = isset($matches[1]) + ? trim($matches[1]) + : $sort_expression[$index]; + $sort_direction[$index] = isset($matches[2]) ? trim($matches[2]) : ''; + } } else { - $sort_expression = $sort_expression_nodirection = $sort_direction = ''; + $sort_expression[0] = $sort_expression_nodirection[0] = $sort_direction[0] = ''; } return array($sort_expression, $sort_expression_nodirection, $sort_direction ); - + } // end of the '_getSortParams()' function diff --git a/sql.php b/sql.php index 5006dc8b65..b934d4704d 100644 --- a/sql.php +++ b/sql.php @@ -26,6 +26,7 @@ $scripts->addFile('jquery/jquery-ui-timepicker-addon.js'); $scripts->addFile('tbl_change.js'); $scripts->addFile('indexes.js'); $scripts->addFile('gis_data_editor.js'); +$scripts->addFile('columndelete.js'); /** * Set ajax_reload in the response if it was already set diff --git a/test/classes/PMA_DisplayResults_test.php b/test/classes/PMA_DisplayResults_test.php index 3aa9f188f2..6c25e19d09 100644 --- a/test/classes/PMA_DisplayResults_test.php +++ b/test/classes/PMA_DisplayResults_test.php @@ -723,13 +723,21 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase public function dataProviderForGetSortParams() { return array( - array('', array('', '', '')), + array('', array(array(''), array(''), array(''))), array( '`a_sales`.`customer_id` ASC', array( - '`a_sales`.`customer_id` ASC', - '`a_sales`.`customer_id`', - 'ASC' + array('`a_sales`.`customer_id` ASC'), + array('`a_sales`.`customer_id`'), + array('ASC') + ) + ), + array( + '`a_sales`.`customer_id` ASC, `b_sales`.`customer_id` DESC', + array( + array('`a_sales`.`customer_id` ASC', '`b_sales`.`customer_id` DESC'), + array('`a_sales`.`customer_id`', '`b_sales`.`customer_id`'), + array('ASC', 'DESC') ) ), );