Sunday, 12 June 2011

Vocabulary by machine name, taxonomy_get_tree() and $user

(Amended on 17 Jul 2011 to replace reloading entire entities with using field_attach_load().)

Getting the ID of a vocabulary has long been a problem in Drupal but Drupal 7 goes a long way to resolving that:

$voc = taxonomy_vocabulary_machine_name_load('vocab_machine_name');

And you've got it.

However if you want to load a taxonomy_tree you still have to use the vocabulary ID so you run the line above first, and then:

$tree = taxonomy_get_tree($voc->vid, [parent], [depth], [internal value]);

The last two parameters on taxonomy_get_tree() have been swapped so if you want to specify the depth you don't have to do (..., -1, $depth) just (..., $depth). Which is handy.

Note that taxonomy_get_tree() does not return a full entity, which is unfortunate if you want any fields that have been attached to the terms. You have to fetch the fields separately but it's not hard:

$tree = taxonomy_get_tree($voc->vid);
foreach ($tree as $key => $term) {
  $terms[$key] = $term->tid;
field_attach_load('taxonomy_term', $terms);

The same applies to the global $user variable. This is normally the user without attached fields, so if you need a field that has been attached...

global $user;
field_attach_load('user', array($user->uid => $user));

Hope that helps.

Thursday, 9 June 2011

Contextual links

Today wasn't the first time I've had a fight with contextual links, but I decided that this time I would get to the bottom of why they are such a pain to implement.

What's a contextual link? The little on-screen tools that let you jump to editing/deleting nodes, views, configure blocks and so on. (But not taxonomy - however I'll get to that later.)

The idea behind a contextual link is as simple as it is useful. If you've viewing a node teaser somewhere on the page then, if you have the permissions, a little dropdown menu will be available that lets you edit that node. Or if it's a block you can configure the block. If it's a Views block you can either edit the view or configure the block.

And so on.

But what if you want to add your own contextual links?

If you are filling a custom block then it's not very hard. I had a situation where I displayed a block which contains some text pulled from a node - but the node can vary depending on the current setting of site taxonomy.

I wanted to add a contextual link that would allow an administrator to edit the text. So I needed to create a contextual link that would link to the currently displayed node:

$build = array(
  '#contextual_links' => array(
    'node' => array('node', array($node->nid)),
 ... rest of the content

The first 'node' is the module that provides the links we want, the second 'node' is the first part of the link URL while the array contains any further parts to add to the link URL.

So what we get here is node/nid/edit and node/nid/delete - the contextual links module figures out what URLs the current user can access from the base supplied URL (in this case node/nid). If the user can edit the node, but cannot delete it he'll only get the 'edit' link.

What this means is that you can't just add any old links, you have to work with the system.

Why is the second parameter an array? Because you might have more than one variable value - these are arguments for a URL, so for a block's contextual links you have:

'block' => array('admin/structure/block/manage', array($block->module, $block->delta)),

So far so good.

For my current project I needed to create a list of taxonomy subjects and I wanted to allow the name of the term to be editable. This is where things started to get hard. It was hard because I did not know three things:

The first thing to understand is that contextual links only work for template themes - not for function-based themes. The reason has nothing to do with the contextual module - it's because generic preprocess theme hooks are only called for templates. And the contextual module uses a generic preprocess function to find the elements that require links.

The second thing to understand is that your template must contain "render($title_suffix)" because that is where the contextual links are contained and they are not rendered until you specifically render them. You can use block.tpl.php as an example.

This was all fine except for the third thing: my taxonomy link would not display even though I thoroughly checked it's format and tried other types of contextual link in the same location which all worked fine. Just not the taxonomy/term/[tid]/edit.

Luckily I'd been reading up on everything to do with contextual links and remembered that someone somewhere had mentioned the 'context' setting in hook_menu(). This is the third thing that has to be in place for a link to appear, so I added a hook_menu_alter() function:

function mymodule_menu_alter(&$items) {
  $items['taxonomy/term/%taxonomy_term/edit']['context'] = MENU_CONTEXT_INLINE;

And suddenly my link appeared.

Finally there is the fact that if you want to put in a node create link more hacking is needed - although it's all legal Drupal. I picked this up from this blog. Essentially you have to make the URL appear to be a child item like this (in hook_menu_alter):

$items['node/add/page']['_tab'] = TRUE;
$items['node/add/page']['tab_parent'] = 'node/add';
$items['node/add/page']['context'] = MENU_CONTEXT_INLINE;

As it says on Marcus's site, it's not clear whether this will actually break the menu system, but it doesn't seem to.

I hope that's some help.