Pressflow minor versions and double leading slashes

Between Pressflow 6.22.102 and 6.22.104, there was a small but substantial change. The url() function no longer normalizes leading slashes.

This brought it back inline with Drupal 6.x behaviour, but has also led to a number of our sites showing links with two trailing slashes. Browsers interpret <a href=”//…”> as a reference to a domain, not a resource (so as if it were http://…) and this leads to broken URLs.

The problem arises when a Drupal path is passed through url() (or calling functions like l()) more than once. This can happen in your own code, or it can happen in Views: if you use Views fields, and render a path out, but then e.g. exclude it from display and use its token elsewhere.

If you’re building your own links with Views fields, don’t retrieve a node path field [path] for use, as this immediately gets rendered with a leading slash and is then unuseable as a link token elsewhere: it will get a second leading slash. Instead, get the bare node ID [nid], and build links of the form node/[nid]. When these get passed through url() one time only, they get turned into the friendly [path] aliases anyway.

Blog category:

Defining customer profile weights in Drupal Commerce

Drupal Commerce is a great leap forward from Ubercart. It’s especially heartening to see it use so much of other APIs - Views, Rules etc. - rather than doing its own thing. However, we think we’ve found a bug in the way that it sorts the fields and panes on the checkout page.

The commerce_customer sub-module takes customer profile types (defined via hook_commerce_customer_profile_type_info()) and prepares them for inclusion in the checkout pane. When doing so, it also transfers a weight field called checkout_pane_weight, so that in theory these types can be weighted (to make e.g. contact form appear before billing address form).

However, checkout_pane_weight is not used anywhere else in the commerce codebase as far as I can see. When commerce_checkout_panes() merges in an array of defaults, it includes a simple ‘weight’ field, but not ‘checkout_pane_weight’.

Until this bug is fixed, you can work around it with the following hook in your own module:

 * Implements hook_commerce_checkout_pane_info_alter() 
function MYMODULE_commerce_checkout_pane_info_alter(&$checkout_panes) {  
  // Get weights from hook_commerce_customer_profile_type_info
  $pane_weights = cscommerce_commerce_customer_profile_type_info();
  // Loop over them and weight the assembled panes accordingly
  foreach($pane_weights as $pane_key_suffix => $weight_config) {
    $pane_key = "customer_profile_$pane_key_suffix";
    if (array_key_exists($pane_key, $checkout_panes)) {
      $checkout_panes[$pane_key]['weight'] = $weight_config['checkout_pane_weight'];

Note that, because commerce_checkout_panes() increments pane weights by a single digit each time, you don’t get much wiggle room when you’re trying to weight your own panes and they can easily end up all at the top or all at the bottom. 

So if, like me, you just want to swap round two panes, and the single-digit weight increments are messing things up, here’s an even quicker workaround:

 * Implements hook_commerce_checkout_pane_info_alter()
function MYMODULE_commerce_checkout_pane_info_alter(&$checkout_panes) {
  if ($checkout_panes['customer_profile_billing']['weight'] 
      > $checkout_panes['customer_profile_contact']['weight']) {
         $checkout_panes['customer_profile_contact']['weight']) =

This is a simple function that swaps the two weights around in a single assignment step, and it means that if you have weights of 20,19,18,17,16… then you can seamlessly swap your two profiles around to be 20,19,17,18,16… without any recalculation.

Blog category:

When Image Browser loses all its styling

The Image Browser project for Drupal 6.x provides a nice Media-esque modal dialogue for inserting your uploaded images into a rich-text field. However, the transition between the two panes of the modal workflow - image selection, to entering extra details like alignment classes - is handled by Javascript.

This means that if you have an entirely different module, whose Drupal Javascript behavior throws an error, then your whole cycle of behavior calls fall over. The callback between the two panes fails to bind to the form onsubmit, and so submitting one pane leads to an unstyled version of the second one and a change in the URL of the modal iframe.

This happened recently with the Colorbox module. The only solution is to configure its Javascript not to appear on that page: if you’re dealing with a module that has no such configuration options, then good luck….

Blog category:

Diff and Revisioning fight over URLs; you get Access Denied

When Diff is enabled on a Drupal site alongside Revisioning, URLs to the non-current revision on a node e.g. node/194/revisions/3011/view can suddenly have an attack of the Access Denieds.

This arises from the two modules arguing over who actually controls that URL. Diff thinks you’re trying to compare revisions 3011 and NULL of node 194; unfortunately, it wins the argument and decides unsurprisingly that you can’t access revision number NULL.

If you re-weight the modules so Revisioning is heavier, it wins the argument instead, and peace is restored. But maybe there’s a better solution.

Blog category:

Playing with Django: a fretless experience

I've been trying for twenty minutes to shoehorn a joke about Grappelling into this excerpt.

Django continues to gather momentum towards its imminent 1.0 release. The 1.0 beta 1 is out; the developer documentation has been refactored; it already places nicely with Python's powerful debugging and logging tools; indeed, all is proceeding according to the roadmap, more or less. James Turnbull will be speaking about Django 1.0 at the eighth Oxford Geek Night this Wednesday, and it looks like he's got plenty of triumphs to bulletpoint for us.

An Oxford Django sprint had been mooted for this weekend. I didn't hear much more about it, but to be honest I had the great opportunity to actually have my own sprint---against 1.0b1---in work this week, working on a fast-turnaround project. I definitely felt performance improvements, especially when running unit tests. It was also lovely to work on my first internationalized/localized site and to find that it was just a question of dropping in certain bits of middleware to make it work across six languages. We didn't have any translations in place, but I clicked on "Polszczyzna" expecting bugger-all to happen and then suddenly realised that the English-language link read "Anglieski." It's characteristic of Python's (and Django's) refreshingly plastic and just-works behaviour. Magic.

We did encounter one bug, involving model inheritance. I struggled for a while with registering with the project trac to report it. It's my first mediocre experience with Django: I waited a day or so for the arrival of an account-confirmation email, but eventually gave up without adding what would have admittedly been a me-too to an existing bug report. But then, email finally in my inbox, I chased it up just now, to find that it's been fixed. Today.

Probably much like Django itself, the project's interface with the user/consumer requires some past experience with its foibles, but the actual endeavour itself is fast, well-factored and puts most closed-source equivalents to shame.

Installing on the Edge

Happy new year, all. A short and relatively under-researched one to kick off 2007, as I’m suddenly very busy with multiple projects. Just to keep those Google fires burning….

I’m currently taking over a maintenance project from a co-worker. The architecture in question integrates our CMS with Raiser’s Edge, the charity-oriented system from Blackbaud for tracking people, their donations, gifts, bequests etc. It’s a comprehensive but sprawling system, and because of licence limitations all data changes have to go through their equally sprawling API.

A prerequisite of development is that I install the RE client libraries on my machine: actually inside a Windows virtual machine sitting on my Linux box. The first time I tried this, it was over our between-offices ADSL line. That wasn’t terribly successful, owing to the enormous .cab archives included in the distribution. In fact, even stopping it half-way was unsuccessful because of the network delay, so the process had to be killed.

Unfortunately this meant that a later attempt at installation off a CD failed. The error report was, unhelpfully:

Error Number: 0×8004041B
Description: Unknown Error
Setup will now terminate

There’s no clue as to what spoor the previous installation has left behind, nor is there any suggestion of what I might do to fix the problem. Although I’ve since found a solution for the error via Google, I thought it might be good to post it here, along with a possible reason for the error in the first place. Some of the reports of the solution are behind Blackbaud’s login system; some of them are on web feeds that might expire.

The registry key stores something to do with the date that you’re making the installation. To resolve the error, set your computer’s—or your virtual machine’s—clock to January 31, 2006. It probably needs to just be set in the past, but that suggestion worked. Once you’ve changed the clock, perform the install, which ought to go without a hitch. Afterwards, reset the clock to whatever the current date might be.

Why the error popup couldn’t just say that, I don’t know.

Blosxom to WordPress: tying up loose ends

A busy few weeks, but they’ve included an import from a Blosxom blog to a WordPress blog which is worth describing. There are a couple of established methods for importing the data, and I opted for the one that seemed the most modular. This was Eric Davis’ Import-Blosxom method, consisting of a PHP script on the WordPress side and a set of Blosxom flavour files which produce a feed compatible with RSS 2.0. This separation of Blosxom and WordPress behaviours meant that I could thoroughly test the former before proceeding with the latter.

It worked very well with practically no configuration or edits, but there were a few issues with the out-of-the-box behaviour of the import script:

  1. Unicode character entities were being escaped in titles, leading to the exposure of the alphanumeric code e.g. “Z&amp;#252;rich” instead of “Zürich”.
  2. Whitespace in post bodies is converted to hard newlines by WordPress, and so must be excised to avoid tags being broken e.g. ‘<a [newline] href=”…”>’ becoming ‘<a <br/> href=”…”>’.
  3. Multiple hierarchical categories are not supported (a known problem).
  4. Although categories are created and posts are linked to them, the number of posts that a category is used in is not incremented and hence the list of categories on the front-end has zero posts for each category(possibly owing to a change between WordPress versions of how this has been handled).

I’ve come up with a number of fixes that I’ve mentioned both to Davis and on the WordPress support forums. As they’ve been greeted with an eerie silence that I’ve found typical of such forums, I’ll put them up here instead.

To fix the first three problems I created rss_to_wp, a Blosxom plugin that, along with the standard interpolate_fancy package, you can use to wrap your title and category processing bits in the RSS2.0 flavour templates. Respectively, this plugin tackles the above problems by:

  1. Providing an interpolate_fancy method to unescape entities
  2. Normalizing any whitespace in the body of your Blosxom posts to single spaces
  3. Providing an interpolate_fancy method to convert a Blosxom-style category path into a set of category tags

You’ll need to change the Davis-recommended story.rss20 template to implement the two interpolation methods. I’ve made a sample available.

The final issue was a more knotty problem, as it was a bug in the script (possibly caused by WordPress’ handling of categories changing over time). It’s easily fixed by adding a few lines to the category-handling part of import-blosxom.php as follows:

294    if (!$exists)
295    {
296        $wpdb->query("INSERT INTO $wpdb->post2cat (post_id, category_id)
297                      VALUES ($post_id, $cat_id)");
298    }
300    // JPS' addition - increment count if cat ID exists
301    if ($cat_id) {
302        $wpdb->query("UPDATE $wpdb->categories SET category_count = category_count + 1 WHERE cat_ID = $cat_id");
303    }
304    // End JPS' addition

Exit gracefully: exporting and then importing—transporting?—works well if the two tasks are separable. That way the integrity of the exported data can be checked in its transitory state and any bugs worked out, before it’s imported into the new system. It’s certainly worthwhile backing up the target database for the import, as this lets you preserve any quirks of your target database if you have to dump all the imported data and start again. The standard WordPress install includes a plugin for doing this, but the command-line tool mysqldump is arguably more powerful.

Subscribe to RSS - bug