Thursday, 15 December 2011

MD5s as IDs

While there is the chance of duplication it can be handy to use MD5s for creating a "unique" ID for strings. I had this in my current project but I've done it before and there is a potential problem which is more likely than duplicate IDs.

There is a chance that you'll get an MD5 that begins with a zero and, potentially even worse, one that begins with a zero and is all decimal numeric (does not contain a-e digits).

In this instance, with the loose typing of PHP, you might find your MD5 gets converted to a number and loses its leading zero (or zeroes). In which case it's useless as an ID and it will take you a very long time to track it down. I know the first time it happened to me it took me a couple of days.

But there is a very simple solution, when you create your MD5 do an immediate search and replace to change all zeroes into 'g'.

$id = str_replace('0', 'g', md5($source));

Now you can be sure you will never lose your leading zero, because there isn't one.

(By the way, after 12 weeks my field_extract module has received no complaints or bug reports so I shall be promoting it to a full version.)

Wednesday, 16 November 2011

Ssssh

I've not posted recently because my current job involves Drupal 6 so I've done virtually no D7 work for a while. However I do have a new set of modules for developers coming soon which will be in both D6 and D7 varieties.

Essentially it's a system that does a similar job to CTools plugins but is lightweight and standalone. It's very good for de-coupling dependent custom modules, essentially very simple but very versatile.

There's a core module that provides the API (a very small module indeed) and then some other modules that demonstrate how to use it and provide handy facilities at the same time.

Hopefully I'll have that out by Christmas. I'm also happy to say that my field_extract module is doing very nicely  in the module usage charts currently standing at 99 sites.

Monday, 26 September 2011

Automatic file download

I have a personal project I'm working on which requires an automatic download link. You know the sort of thing - like you get on SourceForge: "Your file will download in x seconds". And then the file download dialog pops up.

All the solutions for this are JavaScript - and if you want a countdown then JS is the way to go. But I didn't want JavaScript.

The basic part of the trick isn't limited to Drupal at all, you just HTML:

<meta http-equiv="Refresh" content="1; URL=http://www.yoursite.com/system/files/downloadfile.ext" />

So here, after one second, the page refreshes - except it's a file to be downloaded, so your browser does the right thing and gives you a download dialog box instead.

Easy.



Sunday, 11 September 2011

field_extract module in beta

EDIT: Now out of beta. Fully implemented.

EDIT: You can now get the module from http://drupal.org/project/field_extract and it works properly with Drush.

My incredibly useful field_extract module is now available on drupal.org. I am slightly embarrassed because something went wrong in the project creation process so the actual URL is:

http://drupal.org/project/1158878

And when you download it the module comes inside another folder "1158878". In fact this will work just as it comes, you'll just get the folder 1158878 in your modules folder with field_extract inside that.

If you're competent you can extract the inner "field_extract" module and get rid of the outer 1158878 folder but, as I say, it should work out of the box anyway. (I may get around to re-uploading it properly at some point but I can't wait any longer to get it out to you.)

So what does it do? For a start, this is only a developer's module. It does nothing by itself and should only be downloaded if requested by another module. But what it does do is provide a couple of functions that make it much easier to extract field data from entities.

The project page provides clear instructions on how to use it.

Enjoy.

Wednesday, 7 September 2011

Reducing the footprint

Sorry I haven't blogged in a while, I have been working very hard on commercial D7 sites in my day job but haven't really come across anything spectacularly D7 I wanted to talk about.

So here's something unspectacular but quite important, and applies almost as much to D6 as D7.

Every module eats memory. Every x.module file (for enabled modules) gets loaded for every access (well, let's ignore caching). So anything that can be done to reduce the files loaded (size in this case) can only be a good thing. (Back in the day I wrote code for machines with 128K of memory of which 20K might be used for the screen, and another 32K for the OS - in those days you really had to think about keeping code as compact as possible.)

Drupal uses hooks to gather information from modules, and then it stores this information in the database caches (or other tables), after which it no longer calls these hooks - unless the caches get cleared and it has to build the information again.

Hooks like hook_menu, hook_theme, hook_permission and so on. But these hooks need to be available in the x.module file - and yet they are only called infrequently.

Solution? Put a minimal hook in the x.module file which loads another file (say x.registry.inc) and then calls the actual hook code which is in there. Like this:

In x.module:

function x_menu() {
  module_load_include('registry.inc', 'x');
  return _x_menu();
}

And in x.registry.inc:

function _x_menu() {
  $items = array();


  $items['my/path'] = array(
    // my/path data  );


  return $items;
}

If a hook is called on pretty much every page there is no advantage in doing this. But if it's a hook that's called infrequently it's definitely worth it.

This is something I'd played with back in my D6 days but then stopped using. But then I saw another contributed module that was using it and thought "yep, I should be doing that". So I am and here it is.

hook_hook_info()

There is another solution using hook_hook_info() which allows you to define a file in which hooks can be found. This does not work for the majority of system modules because they use customised hook-calling methods. Only one system module - Tokens - has it defined so token code can be located in x.tokens.inc.

However the Block module only uses standard hook-calling functions and you could specify a file for block management using hook_hook_info_alter(); I wrote something about this here.

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.