Tag Archives: framework

What you need to know about Drupal views

Every content management system needs its query builder—an application which creates customizable lists of content elements and present them in a similarly customisable way. Customization will be typically through an admin interface on the website, and the query builder can be as basic as a text box for SQL or as complex as a many-layered GUI across multiple webpages. Drupal, for its part, has Views. D5 has Views 1, D6 has Views 2, and it looks like the forthcoming Drupal 7 will have Views 3.

However, as the criteria for list-building get more and more complex, there will come a point where custom code will produce the exited result far more quickly and efficiently than Views. The problem for the developer is: while simple lists are obviously candidates for Views; and riotously tangled layouts are obviously candidates for your own modules and SQL; how do you cope with the middle ground? How can you tell from the outset that a particular problem will land you in a Views-only cul-de-sac?

Well, I contend that the individual developer needn’t worry, so long as he’s clued up about developing with, not outside, Views. I reckon that the middle ground can almost always be bridged by Views augmented with custom code; unless you’re completely convinced from the start that, say, the logic of what you want is not representable in a query builder, you can start with Views and tweak with customisations later.

But to be sure you can extricate yourself from any small to medium tiger traps that “Views+your spec” might drop you in, it’s wise to know in advance the full range of tools at your disposal. Here are five of what I consider the most important tricks for building a complex Drupal view.

  1. Views themeing  Views has a very modular set of Drupal theme files. The markup and presentation of individual fields, rows of fields, and whole views of many rows can all be modified on a per-view, per-view-format or even cross-site basis. In a particular view’s admin GUI, you can see an entry marked “Theme: Information.” If you click on this, it will tell you what template files the view will try to use, and what files it’s currently using (in bold.) You can copy these files across from the module directory to your theme and rescan to use them; you can even rename them to be specific to the current view or formatting options, and when you rescan the directories, Views should spot the new files.
  2. Addon modules  There are a number of extension modules for views which will very quickly improve view presentation or data flexibility. At the one end is Semantic Views, which allows you to present certain fields in your views with more semantic markup: titles can be h2 elements; contact details can be in address elements, etc. At the other end of the workflow, close to the database, is the Views Or plugin, which lets you swap the normal views filter AND logic (“all items must be published AND have type=blogpost”) for a more inclusive OR logic (“all items must have type=blogpost OR be less than three weeks old”). If you’re not sure whether there’s an addon module for you, ask on the #drupaluk IRC channel.
  3. Views as a backend  Several third-party modules use views as an adjunct: that means that while the module itself is doing something apparently clever with your content, it’s secretly using a view to manage most of the hard work. If you want to maintain arbitrary admin-sortable lists of content, Nodequeue provides a nice interface for doing so. But behind each nodequeue sits… a Drupal view, and a plugin which ties the nodequeue database table to your nodes to produce the required results. Tagadelic, a module which produces tag clouds for e.g. your recent blogposts, has over the years moved from a standalone module (which still exists), to a Views style plugin, so you can build the initial tags view yourself and activate Tagadelic styling and popular-tags filtering once you’re done.
  4. Views hooks  The workflow for views is like the workflow of Drupal itself in miniature: as such, it has its own set of views-specific hooks, including hook_views_pre_render and hook_views_pre_build. All the Views hooks are documented on drupalcontrib.org. So, learn the basic page-view flow, from a blogpost by Mr Views himself, Earl Miles. Pick it apart. See what each bit does in the code. You should eventually be able to find the views hook you want in the code, and be able to modify the view object, its compiled SQL, the results returned, and even the eventual output, all in your own modules.
  5. Writing your own plugins  A Views plugin is less complicated than you might fear. There’s not a great deal of easy-to-find, easy-to-read documentation out there about Views plugins, but try not to let that put you off. A plugin consists basically of a module, a hook function (or two) and a PHP object: the rest is just detail and theming! While that sounds a bit glib, it has some merit: look at the plugins for such modules above as Semantic Views and Nodequeue, and see how they all relate back to an object in a .inc file and a template in a .tpl.php file. You can extend an existing plugin using standard PHP class syntax, and develop it piece by piece by overriding methods one at a time. Views plugins are incredibly powerful, more so than using configurable third-party modules or just interrupting the current view using a Views hook. They get your code right into the heart of a given view’s workflow and let you do (almost) whatever you want. Remember to clear caches if you get stuck: you might even need to resort to a “TRUNCATE cache” at your MySQL command line if your changes don’t seem to be having an effect.

If you’ve started building your listing page with Views, but the spec has ended up getting more complicated, try each of the five solutions above in turn. When the problem at hand suddenly gets a whole lot more complicated, escalate your solution’s complexity by moving another step down the list!

There’s obviously a lot more to developing with Views than a single blogpost could ever really summarize without turning into an extended essay. But there’s a lot more documentation out there—although of a pretty sprawling and hard-to-navigate sort—and the tips above should hopefully give you pointers both for where to start your extending of Views, and also what to google for when you get stuck. Have fun and good luck!

Feeds objects within feeds objects

We’ve been doing a lot of work with the Drupal Feeds module recently. The frontend is nice enough, although the sub-navigation was rendered almost illegible by our theme’s CSS. The online tutorials need work, and the admin navigation needs to be made a bit more robust to layout changes; but then it will be the de facto way for people to consume feeds on their Drupal sites.

The most recent work we’ve been doing involved custom integration with RSS feeds arriving effectively as PHP string variables containing all the XML. This is different from either a file on disk or a remote URL: in fact, we had a Python program creating the RSS file from us via a shell (which in turn, horribly, was hitting a remote Oracle database using cx_Oracle). Feeds was definitely up to the job in terms of power. In fact, it was quite a toolkit of useful functionality, which is Drupal code for “incredibly powerful but almost incomprehensible.

It’s not that the developer documentation for Feeds isn’t decent: it’s pretty good. But it’s limited in scope: it tells you roughly how to expose your own Feeds-like objects to the admin interface, but not really how all those objects interact. Most importantly, we wanted to know what happened on a cron run: this is the bedrock of how Feeds works on your site, after all.

I poked around a bit and this is what I discovered:

 

Workflow of a Feeds cron run

Here’s a summary of the above diagram to give you some idea of what’s going on.

 

  1. Drupal’s cron creates a FeedsScheduler object and passes it a “job”, which is all the configuration for a feed call, including any configuration that was attached originally to the particular node which defines the Feed. The scheduler creates a FeedsImporter and passes it the job; the importer then creates a FeedsSource and embeds itself in it as a parent. In each case, the method ::work() is called to create the child/helper object.
  2. The Source object is what now runs the three phases of feed consumption, via its parent Importer. The Source asks the Importer for the relevant Fetcher, Parser and Processor objects: for example, the HTTP Fetcher, the RSS Parser and the Node Processor objects are strung together to turn an RSS feed at a HTTP URL into a set of nodes, one per entry. Each of these have a relevant, verb-like named method: so ::fetch() for the Fetcher etc. The common currency is a FeedsBatch object, which gets passed around and needs to have methods that make it feel like a batch of feed objects.
  3. After the three phases have run, the Source calls hook_feeds_after_import() to do any tidying, then quits to the Importer, which quits to the Scheduler, which then runs its ::finished() method on the job, and the cron run for this particular feed is done.

 

When you build a new plugin, you need to implement hook_feeds_plugins() in a module and reference a class file: this class will be selectable in the admin interface for one of the three consumption phases, depending on what class it’s ultimately based on. You should therefore extend existing classes rather than start from scratch: there are abstract PHP classes in the feeds module directories, which give you skeleton “interfaces” which you can then flesh out with relevant functions. But what’s better is to extend e.g. the HTTP fetcher to fetch from a command on disk (which is what we did) or, say, extend the CSV parser to interrogate JSON.

Class hierarchies mean you don’t have to spend a lot of time reinventing the wheel or hacking existing modules until they become unupgradeable; instead you can take existing classes and tweak them through inheritance, experimenting as you develop.

A Drupal view serving multiple tabs

Drupal’s menu hierarchy is a big and complex beast. It acts as both the repository for registered menu callback handlers (and their associated permissions handlers) and as a way of building more mundane frontend menus for people to click round. It serves both static hierarchical side menus and also dynamic tabbed contextual menus: if you’re on a user’s profile page, there are “View” and “Edit” tabs, with “Account” and maybe “Profile” sub-tabs under “Edit”; yet this menu hierarchy doesn’t exist in any real sense. This complexity, and this slight disconnect between all the various bits of menu.inc and menu.module, means that menu often gets exposed to other modules in a counter-intuitive way.

Say you’ve got a listing of content (using Views, naturally). You want this listing to sit at /posts . But you also want an advanced search to sit at /posts/advanced , and you need tabs across the top of the page. Should be easy, right? Create a view, put it on a menu path, in a menu, then create a second view and, oh, I don’t know: put it as a menu tab? As a default menu tab? As a normal menu item under the menu item you just built? Or maybe build a page, and put that on the menu, then create two views as child menu tabs, or maybe one as the default menu tab, or….

After lots of fiddling, I realized you could take advantage of Views’ ability to clone displays within a single view, to solve this fairly straightforwardly.

  1. Start off by building your “Default” display—the initial display a view gives you—so that it matches the field and filter criteria you want to sit at /posts . 
  2. Then create a “Page” display to handle the standard search. Set it to be a normal menu item, at path /posts . Check it’s on the menu hierarchy.
  3. Create a second “Page” display with exactly the same configuration. Under “Page settings”, give it the path ‘/posts/default’ and make it a “Default menu tab” with the parent as “Already exists.”
  4. You now have a tab to handle your default, non-advanced search, and a menu hierarchy entry to tie it to. Further tabs should now be straightforward.
  5. Your advanced search can now be created as a third “Page” display. As with #3 above, give it a path ‘/posts/advanced’ and make it a “Default menu tab” with the parent as “Already exists.”
The trick here is to remember that two tabs need four view displays: the view’s core display; a display that never gets seen but sits on the menu hierarchy; and two displays for the standard and advanced tabs. There are other ways to do this with fewer view displays, but only by having e.g. a module handle the main menu entry or one of the tabs. Ultimately two tabs need three menu handlers, and three menu handlers needs a four-display view.

 

Automated backups to S3

Simon Willison put together an excellent short-and-simple backup script over a year ago now, and I’ve used it intermittently to make backups. It takes files, whole directories, or the output of shell commands, and wraps it all up into a datestamped gzip file, before sending the whole thing up to an Amazon S3 account. It’s a nice little piece of kit, in other words.

I found that it worked mostly fine, although it was (a) fiddly to run as a cron and (b) threw occasional errors which I wanted to fix. These two things together meant I wanted to do some proper development on the script and keep track of it.

I wrote a wrapper to help setting up cronjobs, which means it can be scripted using config files in the user’s home directory and . I then decided on a whim to commit all of this to github , which is the online home for a community that’s been set up around the git versioning tool. As befits a DVCS tool, git has spawned a community which is multithreaded and agile and most of all very friendly, with all free repositories available to the public and everyone able to fork off everyone else.

The repository was still a bit empty, as out of respect for the original coder I didn’t commit backup_to_s3.py with everything else. Then, very kindly, Simon agreed to let me bundle his script with everything else, so you can now check out (clone) the “Configured S3 Backups” repository and follow the README to get scripted backups up and running fairly speedily.

I’d love for people to give it a try and offer some feedback. I definitely want to squash bugs and would also welcome new functionality. But the joy of git and github is that if I don’t want to implement any extras, then other programmers can go ahead and

In summary? Configured S3 Backups: give the script a try. Git: give DVCS a try. And github: give DVCS-based online community building a try. Message ends.

Render any block in Drupal 5

Blocks aren’t what you might call “first-class citizens” in Drupal 5 or 6 (or 7, as far as I’m aware). Block functionality is provided by a range of functions and database tables, but ultimately there’s no thingness, no Ding an sich tying them together as an object the way that a node of content or a user might be.

Annoyingly, there’s no function in Drupal 5 to grab a block and render it. You can render a region, a block container which then makes a number of decisions for you about what blocks should appear in it. Blocks can in principle be uniquely identified by (a) the module which provides the block through its implementation of hook_block() and (b) the “delta”, the unique (potentially non-numeric) ID for each block in a given module. Yet there are no functions in the core block module to handle this.

One option is to move to Panels, which I’m doing for this site. Yet if your existing site is heavily based around blocks and regions, implementing Panels can be a lot of overhead just to render a single block in a template.

Here’s a function which returns a single block, given the module name and block delta. It’s an abstraction from block_list(), the function which returns all the blocks in a given region. You can put this in a shell module with an info file (see the previous post on theme preprocess hooks in Drupal 5 for how to quickly set up an otherwise empty module) or if you’re not comfortable with that you can always name it accordingly and put it in template.php

/**
 * Get a single block for themeing
 */
function mymodule_get_block($module, $delta) {
  // User roles mean blocks are still invisible if the user
  // isn't permitted to see them
  global $user;
  $rids = array_keys($user->roles);
  $placeholders = implode(',', array_fill(0, count($rids), '%d'));
 
  // This is still cropped from block_list() , but newlines added
  // for legibility and blogposting
  $result = db_query(
      "SELECT DISTINCT b.* "
    . " FROM {blocks} b LEFT JOIN {blocks_roles} r "
    . "   ON b.module = r.module AND b.delta = r.delta "
    . " WHERE (r.rid IN ($placeholders) OR r.rid IS NULL) "
    . "   AND b.module = '%s' "
    . "   AND b.delta = '%s' "
    . " ORDER BY b.region, b.weight, b.module",
    array_merge($rids, array($module,$delta)));
 
  // Assemble block info from the module
  while ($block = db_fetch_object($result)) {
    // Invoke the block hook in the supporting 
    // module and convert the return array
    $array = module_invoke($block->module, 'block', 'view', $block->delta);
    if (isset($array) && is_array($array)) {
      foreach ($array as $k => $v) {
        $block->$k = $v;
      }
    }
 
    // Swap in any user-defined title in admin interface
    if ($block->title) {
      $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
    }
 
    return $block;
  }
}

Once this is safely out of the way you can grab and render a block. The following code grabs the fourth block that you ever created through the block admin interface (the “block” module is the “maintainer” of those blocks, and the blocks are numbered starting at zero, hence “3″):

<?php print theme('block', mymodule_get_block('block', 3)); ?>

This is a bit unsafe, though. What if you disable mymodule? The template will break, and depending on your server configuration your visitors might see a pretty ugly error. So use the theme preprocess hooks trick. If you want the block to show in node.tpl.php then you’re going to have to bite the bullet and put the bigger code snippet above in a module. Then, in the same module, you can write a hook_preprocess_node as follows:

function mymodule_preprocess_node(&$vars) {
  $vars['myblock'] = theme('block', mymodule_get_block('block', 3));
}

Then you can put just the bare variable in your node.tpl.php, which is much safer:

<?php print $myblock ?>

Turn off mymodule, and this just prints an empty string.

So, ta-da, blocks are now better exposed in your Drupal backend, for you to use in different places in your themeing. Unfortunately that still doesn’t make them first-class citizens: they can’t be categorized or given an “owner” i.e. an associated user ID; they can’t be made to link to nodes in the same robust way that nodes can link to each other with node reference; they certainly can’t have any CCK fields hung off them. But, humble though blocks are, they’ve still got some life left in them yet.

Basic theme preprocess hooks in Drupal 5

One of the useful aspects of Drupal 6’s themeing is theme preprocessing. This occurs in between a module (or Drupal core) calling theme() and rendering the actual template file (or function), and arbitrarily alter the variables passed between the module layer and the theme layer. In this way preprocess hooks act much like Django’s context preprocessors, providing ways for modules to “communicate” at the point of themeing.

Drupal 5 doesn’t have any preprocess hooks, unfortunately. All it has is the function _phptemplate_variables() in your theme’s template.php. That’s the sole instance of theme preprocessing in D5, and this bottlenecking means that the function quickly becomes the dumping ground for all of your site’s pre-theme logic. Even if you’re a module developer, of any level of experience, there’s no incentive to factor this code out elsewhere: you just replace it with whatever the module function’s called anyway, and your site becomes more brittle because of it.

With that in mind, here’s how to free up your D5 theme preprocessing in a reasonably Drupalish manner. We’ll use a helper module, and call a single function, safely from within your template.php. That function will replicate the basics of D6’s theme preprocessing and let you start writing D5 modules with preprocess logic in them.

helpd6.info

First your helper module needs a .info file. It doesn’t need to have very much details in it, but here’s a sample file:

name = D6 forward-compatibility functions
description =  D6 functions, like theme preprocessing

helpd6.module

Next, here’s the module in its entirety. Once you’ve saved this and the .info file into a directory called helpd6, in your site’s modules directory, you should be able to enable the module in your Drupal administrative pages.

<?php
/**
 * Implementation of basic theme preprocessing
 */
function helpd6_implement_preprocess(&$vars, $hook) {
  // module_invoke_all passes by value
  // So assemble function name and invoke ourselves to pass by reference
  foreach(array('preprocess', "preprocess_$hook") as $stub) {
    foreach(module_implements($stub) as $mod) {
      $fn = $mod . "_$stub";
      $fn($vars, $hook);
    }
  }
}

Note we omit the closing PHP tag. And note also that we put an ampersand by $vars. This means we get the “real” theme variables object to play with, not a copy, so that all our logic can modify that “real” variables object and it’s reflected by the time Drupal gets round to themeing.

template.php (excerpt)

Finally, this snippet ties everything together: it calls your helper module from the theme’s template.php. Put it at the very end of the _phptemplate_variables() function:

function _phptemplate_variables($hook, &$vars = array()) {
  // ...
 
  // Finally, run preprocess hook functions
  function_exists('helpd6_implement_preprocess')
    && helpd6_implement_preprocess($vars, $hook);
 
  // Now return variables
  return $vars;
}

You’ll see that it checks the function exists first. You could check with module_exists('helpd6') too, but we’re aiming for as robust a setup as possible, so best check the function exists rather than the general module.

We also return the variables. In practice, referring to &$vars in the function declaration means they get merged in situ, but in principle the function’s definition on api.drupal.org requires the variables array to be returned. So changes inthe PHPTemplate engine might stop the &$vars trick working in the future.

Implementation

You can now implement either hook_preprocess or hook_preprocess_TYPE hooks e.g. hook_preprocess_page. These behave pretty much exactly the same as Drupal 6 preprocess hooks.

As an example, let’s say that some nodes on your site have an “author” field. This might be different from Drupal’s concept of node author e.g: the node might be created by an admin user, but contain information for a book or other publication; the book itself has an author, who could be anybody in the world, alive or dead. Our site also contains a biography node for each author, so we’d like to have the relevant biography present in the theme layer, to list the biography alongside the book.

Here’s a “module”—again, just a single function—that accomplishes just that with the aid of our helpd6 module. It should be self-explanatory, but have a look at the comments for more details.

<?php
/**
 * Implementation of hook_preprocess_node
 */
function authorbiog_preprocess_node(&$vars) {
  // Two possible sources for a biography node:
  // 1. the author is a site user - CCK user ref field
  // 2. otherwise, use a CCK node ref field
  $this_node = $vars['node'];
  $author_uid = $this_node->field_drupal_author[0]['uid'];
  $author_nid = $this_node->field_offsite_author[0]['nid'];
 
  // Load the author's biog into the theme variables
  // (Could use Content Profile to help with this)
  if ($author_uid) {
    $biog = node_load(array('uid' =>$author_uid, 'type' => 'biography'));
  }
  else if ($author_nid) {
    $biog = node_load($author_nid);
  }
 
  // Theme preprocess hooks should ALWAYS make the HTML safe
  // Don't rely on .tpl.php files to filter out possible bad markup
  $biog && ($vars['biog'] = check_markup($biog->body, $biog->format));
 
  // &$vars has been passed by reference, so we don't return anything
  // But you can now access the biog (if it exists) in node.tpl.php as "$biog"
}

You can now access the biography text in the theme layer. Note we don’t pass down the whole node: we could, but it’s safer to pass down filtered HTML, to avoid any possible security holes. In general, it’s the job of theme preprocess hooks to handle HTML security, because you never know where else that HTML is going to end up being rendered.

What’s important to remember is that this code, and many other snippets like it, need no longer live in your template.php. It’s now in a module. It’s something you can turn on and off, just like a module. And it’s something your themer need never worry about: that logic is now safely hidden behind the scenes, leaving template.php as a theme helper, what it’s meant to be. You don’t even need any control logic to determine $hook, because module_implements() and the naming conventions do that for you.

Summary

Drupal 5 themeing will never be as versatile as Drupal 6 themeing. But standard code patterns—especially “Drupalish” ones—can go a long way to abstracting out into modules much of the theme preprocessing that Drupal 5 would otherwise force you to do in your theme layer. The more logic you move out of your theme, the more robust it gets, and the easier it is for a non-developer to maintain.

Building pages in Drupal with Panels

When finished this site will implement several different layouts: blogposts, “static” pages, short “nuggets”, blog archives, taxonomy listing and probably a bespoke front page. Although these will all have the same underlying seven-column layout, that can still present some problems that are usually solved in Drupal with many different theme files and a lot of “regions” in which you can put “blocks” of content.

Regions and blocks have serious limitations, though. For example, any one block—say the most recent posts from the blog, or a feed from Twitter—can only be placed in one region, although it can then be made to appear and disappear on other pages based on URL or (dangerously) bespoke PHP embedded in the database. There are modules which replicate blocks, to try to circumvent such problems. Following merlinofchaos‘ great talk at, once again, DrupalCon Paris 2009, I decided to use Panels and panes instead of the core Drupal region/block system.

It was touch and go initially as to whether I was going to scrap it all and start again with blocks and regions or not. I managed fairly quickly to assemble custom pages for nodes of a few different types, but then had real trouble working out the subtleties of getting custom pages together for existing non-single-node content. Panels and its associated CTools developer framework concentrate far more on front-end functionality than ease of backend user experience. That’s a shame, as the most important take-home message of prioritizing user experience is that hidden functionality might as well be no functionality at all. But as with most of these things—api.drupal.org springs to mind here—the first time is the worst, and once you know what you doing, you, well, know what you’re doing.

Panels lets you divide up a page into just those panels. A set of standard URL matches come with Panels as “pages” you can enable, but you can also add new pages at new URLs. In terms familiar to Drupal templaters they take over the $content variable in the page templates, leaving your existing block regions available if you still want them. So when you enable panels, you get an extra menu item under Site Building called predictably “Panels”. This takes you to a dashboard where you can enable or disable panels support for existing URL schemata (e.g. the node URLs at /node/%) and create variants which take into account e.g. the node type, so your blogposts can look different from your events. 

Slightly more subtly, though, if you want to enable panels on an existing view page… you can’t. There’s no mechanism for overriding the existing view page at e.g. /blog, like you can with a node page, and if you try to create a new panels page at that same URL then the system complains and won’t let you do it. Instead, you have to create a whole new page and eventually discard or at any rate mothball the view’s own page. Assuming you have a view at /blog, create a panels page at /blog_new; you can then add the view as a panel pane. You can use any one of the view’s configuration variants (default, page, block etc.), not just the standard page variant. When everything looks happy, move your view to /blog_old and your new panels page to /blog.

Drupal also provides taxonomy listings at /taxonomy/term/term-id , which with Pathauto get aliased to /category/vocabulary-name/term-name. If you want to override a taxonomy listing, you can… but again there’s a catch. Panels doesn’t seem to provide you with any way of replicating the original content as far as I can see, and instead you have to build a view to replace the behaviour of the original taxonomy listing, and then put that into a pane where you’d want the original dumb taxonomy content to go.

When you create the view, you give it a first argument of “term ID”. Then, when you add the view to the panel, in the modal lightbox titled “Configure view NAME (Defaults)”, under the “Override title” box, you should have a setting where you can choose what to pipe through into your argument. Set that to term ID too. What you end up with is, as seems to always be the case with Panels, more functional and configurable than before, but it’s harder to get to. Not just harder than letting core handle it: that’s to be expected. But harder than you’d expect a GUI-rich many-developer-hour content-driven application to be.

Here’s a summary of my panels the configuration:

Site building > Pages

 

  • System > node_view enabled
  • System > term_view enabled
  • Custom > page_blog created for the blog archives
Node template
  • Node type = blog and Node type = page variants
  • Two-column layout
  • Blog: node content in right column, with custom module providing tags in left column
Taxonomy term template
  • Two-column layout, no variants
  • Term description pane, then  taxonomy-filtering view pane, with view arguments taken directly from /taxonomy/term system URL
Custom view template
  • At URL=blog
  • Paging configured on view
  • View has its own page display variant, at another URL, to provide consistent formatting
CSS
  • Override all core column widths: these are a pain (whoever wants 33:34:33 columns?)
  • Custom CSS classes on some panels and panes, with custom module to also strip down markup

 

Overall Panels has made me feel like I have total control over what I want to appear where on my site. But the learning curve is steep and doubtless puts some people off; some of the greatest advantages come from being happy to get your hands dirty with PHP and the plugin architecture too, which I’ll cover at a later date, and that’s not possible or advisable for most Drupal end users. If I’d not had the luxury of stepping back for a few days every once in a while I’d probably have done it all in templating with regions and blocks. I’m glad I didn’t, though, and I’m looking forward to iterating and perfecting my site’s use of Panels.

Website redevelopment hiatus

The site redevelopment continues, but somewhat slower. Technology has been the main sticking point, and it hasn’t been clear until today whether Drupal (plus the otherwise excellent Panels module) could support exactly what it was I wanted to do. It turns out that it is, but it’s pretty difficult to work out exactly how to configure and build the necessary components.

Like working with so many other OS projects, implementing something for the first time in Drupal can sometimes be a frustrating experience; the second or maybe fourth time is so straightforward that you can hardly believe you ever had problems figuring out how it was done. Once I’ve got some of the key components in place, I’ll have to write some howto blogposts alongside descriptions of the rationale.

Playing with card sorting

If you want your website navigation to reflect the way that users might find your content, you obviously want it to serve their use cases and the terminology they’re comfortable with. Ideally you’d do this by asking sample users to build your navigation for you, but in the absence of any willing volunteers one of the best things a site owner can do is card sorting.

It sounds pretty simple. Take the resources you want people to navigate around—so generally the furthermost leaves of your navigation tree: rich content pages, applications and the like rather than section indexes, which might contain navigational preconceptions—and try to assemble them into groups. Pretend they’re written on cards—or, in a more agile way, actually write them on cards and shuffle them round a table—and try to assemble piles of similar resources. Closed card sorting involves predefining the groups, and is ideal for arranging new elements in an existing navigation and a good compromise for building a new navigation quickly; open card sorting is the same, but with no predefined groups. The results are better but it takes longer.

Card sorting really helps site maintainers escape preconceptions and build a navigational hierarchy that makes sense. So although it seems like overkill, this is actually what I have just done for this website. As with much that I’m currently doing towards the site rebuild, it’s intended as a learning experience; a voyage of discovery, albeit taken on a Tonka truck within the safe confines of the wee sandbox that’s my personal site.

My first pass yielded around fifteen resources—rather fuzzily defined, but including varied things like “my blog” and “a link to my Twitter feed”—which I wanted people to be able to navigate around. I arranged these in a single-parent hierarchy beneath six main headings: ongoing, literature, coding, academia and portfolio. However, this felt a bit forced (and some sections, especially “academia”, are mostly opportunities for old documents to gather dust. I tried to imagine adding a more freeform vocabulary that cut across a lot of this, something like tagging, but there was something wrong.

Unsatisfied, I completely scrapped the single hierarchy and tried again. This time the list of resources had expanded to more like twenty, and I went for a more radical approach to grouping. Instead of trying to group objects as cards in a pile, so each card could only be in one pile, I tried to imagine from the start what groups plural they might fit into. Although I was still thinking in terms of menus, the parent terms were now more like categories from a taxonomy, with any menu item being available from several categories.

This resulted in seven terms:

tech, content, social, lit(erature), work, love and misc

This blog, for example, could fit under “tech”, “content” and maybe “work”, and would be accessible from all those places. My attempts at creative writing on Quiet little Lies could go under “content”, “literature” and possibly “love”.

As long as the number of objects didn’t get too long (and objects didn’t spread under too many terms) then these seven terms could still make sense to the user as a starting point, even if they found different things in multiple places. In fact, in the brave new world of folksonomies and tags, web users might be getting used to less hierarchical ways of navigating, so this might even pay off.

Drupal’s menu system supports multiple or mixed hierarchies out of the box, but I preferred instead to actually use categories, and tie these to a menu with the Taxonomy Menu module. I might not follow that route in future, as it essentially only supports linking to Drupal’s out-of-the-box category index pages, with content sorted in reverse date order, whereas I’l almost certainly want to do something nicer. But like card sorting itself it was a good springboard to getting things working and feeling happy with progress.

For now the hierarchy is implemented with dummy content, but I hope to fill it out more—and then expose the navigation—in the next few days. Right now it’s just nice to see things starting to come together. Also, having seven top-level terms fits in rather neatly with the design decisions I’ve already made: more on those later.

Importing from Wordpress to Drupal

The first stage of building this site was to import the content from the previous site. Well, the first stage was actually to set the site up: to install Drupal and enable the relevant modules. At Torchbox we’ve got a custom install profile that does a lot of this for us, installing and configuring relevant modules and creating users and roles. The actual company profile does a lot more work than I needed, in fact, and I’ve had to pare it back a little so that I’ve got less to maintain and worry about.

Importing content from Wordpress was largely handled by… the Wordpress Import module. There’s a Drupal heuristic that, if it’s a problem that a few people have encountered in the past, there’s probably a module for it. Wordpress instances provide an XML export file called a WXR file, which you put on the filesystem and the module can convert content, freetext tags, the category hierarchy and users/authors.

The one tweak I had to make to the module was required to import the article summaries or excerpts, the little “humourous” quotes that are intended for blog listings. These were present in the WXR file as the <excerpt:encoded> element, and the Wordpress Import module contains a nice utility function that meant I only had to add this code at around line 621 to bring in the excerpt as a CCK field:

$node["field_summary"][0] = array(
  ”value” => wordpress_import_get_tag($post, ‘excerpt:encoded’),
  ”format” => $params['format'],
);

Overall importing from Wordpress was pretty smooth—thanks to both Wordpress and Drupal, to give both technologies their due—but having content on a beta site is a double-edged sword. It’s great to be able to see broadly how the site is going to look when it goes live, with everything in place; on the other hand, it’s disappointing to be able to see broadly how the site is going to break when it goes live. Content showed up all sorts of little bugs: missing, slightly quirky CSS formatting you’d forgotten about; oversensitive output filters; slightly wonky imported URL aliases that needed a visit to the database to fix.

As I iterated and tweaked the configuration on the Drupal site—with content already in place—I had effectively frozen the site development, and could no longer roll back and re-import as it would lose my configuration changes. The import itself meant that for a week or so I was running two sites in parallel, writing blogposts on both, and getting a bit flustered about it all.

I think it was the right, pragmatic decision to do that, even though it initially felt like a lot of overhead: writing some sort of module to do the configuration changes was possible, but didn’t really suit the way I wanted to fiddle with the site rather than run a set of fixes; importing at the very last minute would have meant I’d not have found most of the little irritations until they were publicly visible; not putting content on the blog for a week wasn’t really possible, what with Oxford Geek Night 14 rumbling on, and our most excellent sponsors all clamouring to give us stuff. I just wish, as always, that I’d had twice as much time to do it all in.