1. Twitter pull loses time_ago

    Drupal’s Twitter Pull module is a useful one: not just for its own UI elements, but also for getting tweets to format yourself. However, in a recent version change, it lost its $tweet->time_ago property on the tweet objects.

    You can recreate this straightforwardly using the following PHP:

    $tweet->time_ago = format_interval(time() - $t->timestamp);

    It’s a minor pain but if you’ve exposed your own theme implementations to preprocess hooks - and why wouldn’t you? - then it should be easy to put in e.g. your template.php.

  2. RobotsTxt, robots.txt and /robots.txt

    The RobotsTxt module effectively requires a hack to Drupal core in order to function. This is because core Drupal contains a static robots.txt file, and webservers like Apache are configured by Drupal’s .htaccess file to serve static files preferentially to asking Drupal for the content. Every time Drupal is upgraded (or you deploy the site to a new staging or development instance), that hack has to be repeated.

    One solution which Johan hit upon a while ago was to patch Drupal’s core .htaccess file, to send any request beginning /robots.txt to Drupal rather than serving any file on disk. It still constitutes a hack to core, but as a patch file accessible at a URL it can be incorporated into e.g. drush make files, and applied automatically when Drupal core is upgraded.

    The patchfile-based hack is a big improvement, but it still leaves the RobotsTxt module complaining that there’s a problem. This is because it checks for the robots.txt file on disk, not for the /robots.txt URL, which is the real acid test for whether the module is working properly. So now we’ve also added this patch for robotstxt_requirements(), which checks the URL instead.

  3. 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.

  4. Our Drupal Ladder learn sprint

    Last weekend, ten Oxfordshire Drupalers did a learn sprint at the Torchbox offices. I’ve written a more complete discussion over on the work blog.

    Drupal Ladder learn sprint: pair tuition

    We had a great time: it was a gloriously sunny day, which helped, as we were able to eat lunch (sponsored by Torchbox) on the lawns next to the office. But what was best is that the day was so productive: we all ended up learning together, and by the end of it we could all contribute to Drupal on the issue queues.

    Thanks, Drupal Ladder project!

  5. We’re moving to git flow

    Team Drupal at Torchbox has been using sort-of git flow for ages. Basically, we’ve had to approach git flow, as if from a distance, in order to accommodate e.g. archives we’ve inherited from third parties. But today we committed to standardize on git flow across the team.

    Git flow isn’t really a git extension; it’s more a methodology. Following it is optional: it’s a collection of conventions about naming and meant to help you avoid some of the pitfalls that can be found in any version control system. There’s command-line tools as well, but they’re really all about helping you enforce the conventions you’ve agreed to follow anyway. In an emergency or exceptional situation, you’re always free once again to do whatever’s required to sort things out.

    The Tech Team next door have beat us to it on this - they’ve been using it for ages - but that’s great too, because it means that they’ve been able to recommend it, and so going forwards we hope to use it across the whole company.

  6. Migrating users and profiles with the Migrate module

    At Torchbox we’ve been working a lot with the Migrate module recently. It’s a framework for representing relationships in the data you want to import; both relationships between bits of data, and also relationships between the data and relevant Drupal entities. It used to be a GUI-driven system, but now it seems quite code-heavy.

    Which suits us fine, certainly for one-off imports which can be built most straightforwardly by developers. We had to write an import for around 4600 users, plus data which we wanted to store in three Profile2 profiles (address, subscriptions, personal information.)

    Migrate did a lot of the heavy lifting for us, along with some code examples. The most useful one was this example import module, started by wusel and edited by (among others) Profile2’s maintainer joachim. As a thankyou I’ve contributed to that post a bit, tidying things up and incorporating some of the lessons we learned.

    The results? A flawless migration, including mapping many CSV columns to three multi-valued entity fields for the subscriptions (basically an ORM mapping.) And it was blisteringly fast: user importing on a reasonably high-spec server was at the rate of 12592/min, and we had a peak profile import of 9954/min.

  7. Webform integrates with Entity Token…

    but only from 4.x onwards, it seems.

    We have a specific client requirement on Drupal 7, to use webforms to update both a third-party CRM system and local “Drupal storage”: whatever that ends up meaning.

    It turns out that the excellent Profile2 module provides us with better storage than D7’s core profile module (which doesn’t even use Field API, unlike the rest of D7!) We were always expecting to have to write the CRM/local updating ourselves, but piping those profile values - Profile2 doesn’t modify the $user object in the same way as Profile - into the webforms was a chunk of work we wanted to avoid.

    Luckily, as of the 4.x branch, Webform seems to support token replacement; the Entity Token module (part of the Entity API project) lets entities expose themselves as tokens; and Profile2 uses this to hook itself up to Webform. It all works pretty well, although we’re crossing our fingers for a non-alpha release on Webform’s 4.x branch

  8. 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.

  9. Command-line Drupal at Oxford Drupalcamp →

    I did a talk at Oxford Drupalcamp on what I called “Command-line Drupal”. It was basically Drush, but discussed in a way that non-Drupal people might appreciate.

    My motivation was that, as the Drupalcamp was about education, there were probably attendees who had to administer Drupal sites, but didn’t themselves really know (or care) about Drupal. A command-line syntax might really help them, if it could be demystified.

    Anyway, the slides and notes are on github for anyone to look through, including scripts for two live demos that actually worked. Thanks to Finn and everyone else who organized the camp, especially for letting me switch to a different room at the last minute so I could get a network connection!

  10. Stanley the dog, 1998-2012

    Stanley window Stanley Stanley and Frisbie Stanley shakes hands Stanley Stanley tummy rub Stanley, dark and handsome Stanley, sweety pie Stanley Stanley in the sun Stanley Stanley under Helen's desk

    You’ve been a very good boy. We’ll miss you.