Project:Drupal core
Version:8.x-dev
Component:base system
Category:task
Priority:normal
Assigned:Unassigned
Status:needs work
Issue tags:API clean-up, Framework Initiative

Issue Summary

I noticed that, even though the API supports a "field" element for table columns, the URL will contain the "data" element as a sorting parameter.

As an example, if a header column is defined in the following way:

$header[]=array('data'=>t('Number'),'field'=>'id','sort'=>'asc');

The column head in the table is titled 'Number'. The query string in the link is "?sort=desc&order=Number".

"Number" is not supposed to be an identifying title of the field, that's what "id" is. In fact, if the site is localized to other languages, "Number" will change to other, unpredictable values.

From the documentation of the table API, it seems that it would make more sense if the query string contained "?sort=desc&order=id".

Comments

#1

Version:5.x-dev» 4.7.6
Priority:minor» normal

This also seems to be a problem in 4.7, unless I'm misunderstanding something. Currently, sorting of tables on my 4.7-based site breaks as soon as the column titles are translated.

These tables are generated by Views module, so just figuring out a way to apply t() to the column titles required a bit of work... and now sorting seems to break.

The following code from tablesort_header seems relevant:

$cell['data'] = l($cell['data'] . $image, $_GET['q'], array('title' => $title), 'sort='. $ts['sort'] .'&order='. urlencode($cell['data']) . $ts['query_string'], NULL, FALSE, TRUE);

As a workaround, I may have to incorporate this code -- with slight modifications that will generate the link based on the field name rather than the translated label -- into a theme override.

Anyone with more in-depth understanding of the whole system have thoughts?

#2

I'm not sure how others feel, but I'm not always too keen on letting my database field names be viewed by the public, as its a potentially (although pretty small) security risk (IMOH).

I may be too paranoid here, & in most situations it may be ok to use, but I can see circumstances where it could be an issue & this is possibly why the title was chosen as the sort parameter id? (just a guess)

Perhaps it could default to using either the 'field' (as Arancaytar suggests) or the 'data' (current default behaviour) info by default, and add an optional argument along the lines of 'sort_var' that could be used to force a specific value to use in the 'order=' clause.

#3

Admittedly, my argument about localization doesn't really apply - if the page internally uses t("Number") to compare the get parameter to, that will work. Well, unless the site is localized to two different languages and one user tries to send someone with the other language setting a URL.

Perhaps sort_var really is the way to go.

#4

Title:tablesort_header() - sort url» tablesort_header() - order and sort arguments
Version:4.7.6» 7.x-dev
Category:bug report» feature request

The sort_var addition still strikes me as a good idea. However, this would be an API change, so it will probably have to be delayed to 7.x.

------------

In a related issue, I have problems differentiating between "order" and "sort" every time I use this API. Intuitively, I think that you "sort" by the name in descending "order", but in fact "order" contains the field name and "sort" the direction. Since we're already in 7.x territory, perhaps the parameters could be renamed into something more clear - like "order_by" instead of "order", and possibly "d" for direction instead of "sort".

#5

Version:7.x-dev» 6.2

Hi!

This is still open in the 6.2 release. Maybe the API documentation needs a change?

I spent some time walking through the source code to find out why theme ('table', ...) does not work as expected.

Regarding the comments about sort_var and exposing internal database column names:

Is the "field" attribute of the table's $header array used otherwise than being the sort variable for theme_table()?

#6

Status:active» needs review

According to the api docs for theme_table (for D5 & D6):

$header An array containing the table headers. Each element of the array can be either a localized string or an associative array with the following keys:

* "data": The localized title of the table column.
* "field": The database field represented in the table column (required if user is to be able to sort on this column).
* "sort": A default sort order for this column ("asc" or "desc").
* Any HTML attributes, such as "colspan", to apply to the column header cell.

But if you look at tablesort_header(), we see:

  $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('title' => $title), 'sort='. $ts['sort'] .'&order='. urlencode($cell['data']) . $ts['query_string'], NULL, FALSE, TRUE);

The problem is: urlencode($cell['data']). According to the API docs, that should be: urlencode($cell['field'])- atleast, as far as I can understand it. This seems true for D6 as well.

Supplied patch is for D5.

AttachmentSizeStatusTest resultOperations
tablesort.diff.txt587 bytesIgnored: Check issue status.NoneNone

#7

And here's a patch for D6 head. I haven't tried it out, but should be fine.

AttachmentSizeStatusTest resultOperations
tablesort.d6.txt641 bytesIgnored: Check issue status.NoneNone

#8

Version:6.2» 7.x-dev
Category:feature request» bug report
Status:needs review» needs work

I assume the bug is also in Drupal 7.x, so it must be fixed there.

Also:

  • Please learn how to do a proper patch (unified diff): http://drupal.org/patch/create
  • We also need to modify tablesort_get_order() in order for it to understand the new meaning of the "order" argument
  • While we are at it, some cleanup of tablesort.inc will be rather welcome, as the code is really ugly (for example: $ts['sql'] is not SQL, it is expected to be a field name, but tablesort_sql() sanitize it anyway, etc.).

#9

Title:tablesort_header() - order and sort arguments» Drupal 7: Tablesort Cleanup

Yes, indeed. And while we're at it, we could take the opportunity and improve the API in the following ways:

1.) Require an associative key in the $headers array. This key will be used as the URL parameter, which solves the problem by neither introducing localized text into the URL nor exposing the database schema.

2.) Make order determine only the default direction for that field (for example desc for numbers), instead of marking that column as the default sorted field. Instead, the #sort property in the headers array will contain the key of the field sorted by by default.

Does this sound good?

#10

@Arancaytar: I agree completely.

#11

Category:bug report» task
Status:needs work» active

#12

Please do now. :)

#13

Status:active» needs review

First pass.

AttachmentSizeStatusTest resultOperations
drupal.tablesort.13.patch7.86 KBIdleFailed: 13580 passes, 52 fails, 2102 exceptionsView details | Re-test

#14

Status:needs review» needs work

The last submitted patch failed testing.

#15

Status:needs work» needs review

And this time including all changes.

oh boy, someone should have told me how horrible Forum module really is...

AttachmentSizeStatusTest resultOperations
drupal.tablesort.15.patch18.96 KBIdleFailed: 13643 passes, 8 fails, 192 exceptionsView details | Re-test

#16

Status:needs review» needs work

The last submitted patch failed testing.

#17

Status:needs work» needs review

This one will pass.

Anyone up for reviewing and RTBC'ing?

AttachmentSizeStatusTest resultOperations
drupal.tablesort.17.patch21.37 KBIdleFailed: Failed to apply patch.View details | Re-test

#18

Status:needs review» needs work

I don't have much experience with the tablesort API, so I'd like somebody else to sign off on this, but here are some comments.

+    if (!empty($ts['field'])) {
+      // Remove table prefix from sorting field.
       // Based on code from db_escape_table(), but this can also contain a dot.
-      $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
+      $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['field']);
I don't understand the added comment. AFAICT table prefixes are not stripped.

+    if (!is_array($column) || !isset($column['field'])) {
+      continue;
+    }
+    // Use the header column matching the URL parameter.
+    if ($order == $column['field']) {
+      return array('name' => $column['data'], 'field' => $column['field']);
+    }
+    // In case no header column will match the URL parameter, and this column
+    // defines 'sort', it is supposed to be the default sorting column.
+    if (isset($column['sort']) && ($column['sort'] == 'asc' || $column['sort'] == 'desc')) {
+      $default_column = array('name' => $column['data'], 'field' => $column['field']);
+    }
Doesn't this overwrite $default_column in each iteration, so it ends up referring to the last column with a valid sort attribute? If yes, is this intended? The first column would be a better default.

Do we need to check for isset($column['data']) as well?

+  // If we end up here, then a table header did not define any valid tablesort
+  // data.
+  throw new Exception(t('Invalid TableSort data; header needs to define at least one sorting field.'));
+  return array('name' => '', 'field' => '');
The return is never reached. Though I personally like that Drupal complains loudly in case it detects a developer mistake, it seems that the current convention is not silently ignore cases like these.

+        $ts = tablesort_init($header);
It's a terrible variable name (I know you didn't introduce it).

-  foreach ($headers as $header) {
...
+  foreach ($header as $column) {
I think the variable name $column describes the variable better than $header, but if you change it, I think you should change $headers to $columns. AFAICT $headers is an array of $header variables, so a simple plural form seems like the obvious choice.

Some other clean-up ideas (possibly outside the scope of this issue):
How about renaming tablesort_init() to e.g. tablesort_get_parameters() - the function simply returns stuff and does not initialize anything. Likewise for TableSort::init().

How about killing tablesort_get_sort(), tablesort_get_order() as separate functions and instead just move them inside tablesort_init()? It seems that you rarely need these values alone without the rest of the array returned by tablesort_init(). Likewise for their counterpart methods in TableSort, getSort()/order()/init().

tablesort_header() and tablesort_cell() take an array as argument (by value), modifies it and returns it. It seems more in line with Drupal standards to accept the argument by reference and just modify the original array, i.e. change tablesort_foo($array) to tablesort_foo(&$array).

#19

Status:needs work» needs review

I don't understand the added comment. AFAICT table prefixes are not stripped.

That was contained before already. I'm not sure whether it will work without that, but let's see. :)

Doesn't this overwrite $default_column in each iteration, so it ends up referring to the last column with a valid sort attribute? If yes, is this intended? The first column would be a better default.

Do we need to check for isset($column['data']) as well?

1) No. As the comment explains, and as explained elsewhere in API docs, only the default sorting column in the table's $header should define 'sort'. All other columns do not specify a sort.

2) Since we explicitly check that the column in $header is an array, 'data' must be set. @see theme_table()

The return is never reached. Though I personally like that Drupal complains loudly in case it detects a developer mistake, it seems that the current convention is not silently ignore cases like these.

I agree on both, and with the new exceptions, we should start to think about DX. Removed the return.

I think the variable name $column describes the variable better than $header, but if you change it, I think you should change $headers to $columns. AFAICT $headers is an array of $header variables, so a simple plural form seems like the obvious choice.

Nope, $header is documented in theme_table() and follows the structure documented in there. It is the same $header, so we keep that variable name.

The other clean-ups you mentioned we should defer to another issue. This patch is large enough. :)

AttachmentSizeStatusTest resultOperations
drupal.tablesort.19.patch21.36 KBIdleFailed: Failed to apply patch.View details | Re-test

#20

Status:needs review» needs work

I don't understand the added comment. AFAICT table prefixes are not stripped.

That was contained before already. I'm not sure whether it will work without that, but let's see. :)

I was only referring to the additional comment you added (// Remove table prefix from sorting field.), not the code itself. Now that you removed the preg_replace() call, it seems that the user-provided string ends up unescaped in the SQL query.

2) Since we explicitly check that the column in $header is an array, 'data' must be set. @see theme_table()

I'm not sure I follow you. The documentation for theme_table() says this about $header:

* @param $header
*   An array containing the table headers. Each element of the array can be
*   either a localized string or an associative array with the following keys:
*   - "data": The localized title of the table column.
*   - "field": The database field represented in the table column (required if
*     user is to be able to sort on this column).
*   - "sort": A default sort order for this column ("asc" or "desc").
*   - Any HTML attributes, such as "colspan", to apply to the column header cell.
I'm not sure whether this means that all the mentioned keys are required when passing an array. It doesn't say so very explicitly. At least the code seems to check for "field" and "sort" using isset(), but not for "data".

Nope, $header is documented in theme_table() and follows the structure documented in there. It is the same $header, so we keep that variable name.

I still think it's a bad name and I think we should change theme_table() :-) But fair enough, we can deal with that later. It's just an internal variable name, so we can change it without breaking any APIs.

#21

Status:needs work» needs review

This patch was at 99% and only hold off by nitpicks. :(

Two other issues made me revisit this issue:

#615822: Benchmarking / profiling of D7
#602522: Links in renderable arrays and forms (e.g. "Operations") are not alterable

Due to the latter patch, I suspect that this patch will no longer apply, but let's see what the bot thinks.

#22

Status:needs review» needs work

The last submitted patch failed testing.

#23

subscribing

#24

subscribing

#25

This is definitely not done. This is simply an attempt to reroll the previous patch since alot has changed in the last month and a half. I'd work some more on it now, but I have to step away for a bit.

AttachmentSizeStatusTest resultOperations
97293.patch19.23 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 97293.patch.View details | Re-test

#26

Anyone that wants to play with it, feel free. I won't be able to for a while.

#27

For reference #664042: TableSort order error when no sort set in header affects this issue if it gets in this patch may have to be rerolled.

#28

Status:needs work» needs review

CNR for bot run of #25. Is this still on the table for D7?

#29

Status:needs review» needs work

The last submitted patch, 97293.patch, failed testing.

#30

Component:theme system» base system
Assigned to:Anonymous» sun
Status:needs work» needs review

Re-rolled against HEAD. My main interest is to get includes/tablesort.inc out of the regular full bootstrap request path. It's needlessly loaded, on each and every single page.

AttachmentSizeStatusTest resultOperations
drupal.tablesort.30.patch21.06 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch drupal.tablesort.30.patch.View details | Re-test

#31

Status:needs review» needs work

The last submitted patch, drupal.tablesort.30.patch, failed testing.

#32

Status:needs work» needs review

#30: drupal.tablesort.30.patch queued for re-testing.

Not sure why the locale test is failing... Let's ask the bot one more time.

Huge +1 for this. I've been looking at the tablesort code while implementing #885014: Add pager and tablesort to EntityFieldQuery, and noticed the problems described...
Reviewed the patch, looks excellent.

#33

Status:needs review» needs work

The last submitted patch, drupal.tablesort.30.patch, failed testing.

#34

Status:needs work» needs review

#30: drupal.tablesort.30.patch queued for re-testing.

#35

Status:needs review» needs work

The last submitted patch, drupal.tablesort.30.patch, failed testing.

#36

Status:needs work» needs review

#37

Status:needs review» reviewed & tested by the community

Gave this another review, the fixes are awesome and really needed.
I don't like the fact that we need to require_once tablesort every time we plan on using it, but I guess that's unavoidable (on the other hand, including it on every page even when not used is completely stupid)

#38

+++ modules/forum/forum.module 25 Sep 2010 14:33:52 -0000
@@ -1058,20 +1044,20 @@ function template_preprocess_forum_list(
+  // Render the table header; this is required, because the forum topic list
+  // table does not use theme_table().
+  require_once DRUPAL_ROOT . '/includes/tablesort.inc';

Note that it's only Forum module that needs to load tablesort.inc manually. As the comment tries to explain, Forum module does not use theme_table(), and on top of that, it tries to output a sortable table also for a forum container, even when there are no topics (since otherwise forum_get_topics() would have been invoked, in which the select query is extended with TableSort, which in turn autoloads tablesort.inc), and therefore Forum module has to load tablesort.inc manually.

That's a total edge-case, and should be fixed in a completely different way: Update Forum module to make sense.

Powered by Dreditor.

#39

Thanks for the clarification. Let's get this in!

#40

#30: drupal.tablesort.30.patch queued for re-testing.

#41

Status:reviewed & tested by the community» closed (won't fix)

#42

Assigned to:sun» Anonymous
Status:closed (won't fix)» reviewed & tested by the community

Just because someone is upset doesn't mean we have to mark something as won't fix. Someone else is more than welcome to pick up work on a ticket.

#43

Version:7.x-dev» 8.x-dev

Although badly needed, this sounds like D8 material to me.

#44

Version:8.x-dev» 7.x-dev

Can we ask Drieschick first?
If there are parts that are D8, maybe they can be postponed, and the other chunks can get in.
It's a good cleanup and a simple fix to an annoying bug. I actually see nothing controversial about it.

I'm prepared to push this forward (even if it gets moved to D8 in the end).

#45

Version:7.x-dev» 8.x-dev

And... This is definitely off the table :/

#46

Referencing back to a case addressing 7.x minimal bugfixing for tablesort functions.
#295430: Default sort using tablesort_get_order() is broken.

#47

#30: drupal.tablesort.30.patch queued for re-testing.

#48

Status:reviewed & tested by the community» needs work

The last submitted patch, drupal.tablesort.30.patch, failed testing.

#49

Title:Drupal 7: Tablesort Cleanup» Tablesort cleanup
Issue tags:+Framework Initiative

#50

Here's a re-roll of #30 that applies cleanly, but now has failing unit tests that need to be updated to work with the cleaned up API.

AttachmentSizeStatusTest resultOperations
tablesort-97293-50.patch18.15 KBIgnored: Check issue status.NoneNone

#51

Subscribing

It's really annoying.

#52

The patch from comment #50 touches a lot of files. Attached is a tighter D7.9 fix isolated to tablesort.inc and entity.inc.

Changelog:

Use field to determine sort order: Rewrite tablesort.inc:tablesort_get_order()

  • Compare header[field] to _GET[order] (instead of data)
  • Remove ts[name] - not needed
  • Rename ts[sql] to ts[field] - more internally consistent and not exposed to callers

Decorate column header with sort indicator and URL based on field: Rewrite tablesort.inc:tablesort_header()

  • Compare cell[field] to ts[field] (instead of data)
  • Use ts[field] instead of ts[data] to construct column click sort query params

Decorate cell based on column field: Rewrite tablesort.inc:tablesort_cell()

  • Compare header[field] to ts[field] (instead of data)

Determine sort direction based on field: Rewrite tablesort.inc:tablesort_get_sort()

  • Use header[field] and ts[field (instead of data)

Query database using field: Rewrite TableSort::orderByHeader()

  • Use ts[field] instead of ts[sql]

Fix callers
entity.inc: EntityFieldQuery::tableSort()

  • Rename $order to $ts for code consistency
  • Compare header[field] to ts[field]

Please be gentle this is my first patch and I've only been coding in anger w/ Drupal for about 3 weeks.

AttachmentSizeStatusTest resultOperations
tablesortfix-97293-52.patch4 KBIgnored: Check issue status.NoneNone

#53

The are a few issues I needed to handle in addition to using [field] in the sort URL:

  1. Decouple the field ID and database query column
  2. Allow sort order to be reversed (ORDER BY ASC appears with decending indicator)

I've added support for this with the attached D7.9 patch using two new header column attributes: query_field and query_reverse.

As an example of #1, consider the scenario where a item date is stored in a database datetime column, however you would like to show the date and time in two separate table columns. You might use the following $header definition

<?php
$header
= array(
  array(
'data' =>'Date','field' => 'date', 'query_field' => 'post_datetime'),
  array(
'data' =>'Time','field' => 'time', 'query_field' => 'post_datetime'),
);
?>

For these two columns, the URL will be ?order=date and ?order=time, however on the backend TableSort extension is adding an ORDER BY post_datetime clause to the SQL query.

#2 should be self explanatory, just add 'query_reverse' => TRUE

See attached patch.

AttachmentSizeStatusTest resultOperations
tablesortadd-97293-53.patch4.52 KBIgnored: Check issue status.NoneNone
nobody click here