Sorting unsortable node and comment links

Professional Article
October 26, 2013

This will be a very short article where I'll just share a small problem I've encountered recently and how I've solved it.

I had a small task: I needed to sort the links in a forum node. I thought it would be an easy one, but heh, you have to love Drupal :)

First idea was to go in the theme_preprocess_node() (or, in case we use Omega and we follow best practices, we create preprocess-node.inc file under the preprocess folder) and to change the '#weight' of the links as I like it.

But if you just check how drupal_pre_render_links function works, we'll see that it just loops through the elements, igoring their '#weight' what so ever. So, which module had a higher weight in the system table, will have its links show up as last, as it was the last module which attached something to the node's content. Some guys offered solutions as using array_shift / unshift / pop, etc. so you would alter the content links array as you wish and thus the order of the elements - I found it a dirty, so I came up with an alternative solution.

Anyway, here's how the forum nodes' content array looks like ($vars['content'] in preprocess_node):

And here's our sweet drupal_pre_render_links function:

What we really want to do, is to change this pre_render function to one of ours, where we actually sort the links before we join them into one big renderable array.

  1. case 'forum':
  2. if ($vars['view_mode'] == 'full') {
  3. // Handle the sorting.
  4. $vars['content']['links']['#pre_render'][0] = 'MYTHEME_pre_render_sorted_links';
  5. }
  6. break;

Our new MYTHEME_pre_render_sorted_links function looks like this:

  1. /**
  2.  * Overriden drupal_pre_render_links function which also handles sorting.
  3.  */
  4. function MYTHEME_pre_render_sorted_links($element) {
  5. $element += array('#links' => array());
  6. uasort($element, 'element_sort');
  7. foreach (element_children($element) as $key) {
  8. $child = &$element[$key];
  9. // If the child has links which have not been printed yet and the user has
  10. // access to it, merge its links in to the parent.
  11. if (isset($child['#links']) && empty($child['#printed']) && (!isset($child['#access']) || $child['#access'])) {
  12. $element['#links'] += $child['#links'];
  13. // Mark the child as having been printed already (so that its links
  14. // cannot be mistakenly rendered twice).
  15. $child['#printed'] = TRUE;
  16. }
  17. }
  18. return $element;
  19. }

And now, we want to actually give our elements '#weight' values and see how those get sorted (refresh the page twice).

  1. case 'forum':
  2. if ($vars['view_mode'] == 'full') {
  3. // So we want to change the weight of the links and sort them,
  4. // this is why we use other pre_render function.
  5. $vars['content']['links']['flag']['#weight'] = -10;
  6. $vars['content']['links']['statistics']['#weight'] = -8;
  7. $vars['content']['links']['comment']['#weight'] = 10;
  8.  
  9. // Also move the node subscription link.
  10. $vars['content']['links']['node']['#weight'] = -9;
  11.  
  12. // Handle the sorting.
  13. $vars['content']['links']['#pre_render'][0] = 'MYTHEME_pre_render_sorted_links';
  14. }
  15. break;

​​​​​​​Before:

After:

Anyway, I hope this will be helpful for others as well. This is better than shifting keys in $vars['content'] array.

Comments:

Feel free to ask any question / or share any suggestion!