drupal

Fuzzier (human-friendly) timestamps with Drupal 6.x

I set up super-sweet fuzzy "submitted by" timestamps on my blog a couple of days ago. They're quite a bit easier to grok at a glance, and they show the full date when you hover over them.

Go on, try it with the one at the top of this post. You know you want to.

Drupal 6.x makes tweaks like this easy. Here's how you can do it too:

Open up your theme's template.php file. If you're using a stock theme, copy the theme from Drupal core into your site's themes folder before editing anything.

Add these three functions:

/**
 * Calculate a fuzzy delta between two timestamps. If only one timestamp is
 * passed, uses 'now' as the other. Optionally takes a third 'granularity'
 * parameter, which decides how many chunks to return. Default: 1.
 */
function _fuzzy_delta($to, $from = null, $granularity = 1) {
    $times = array(
        'year'   => 31536000,
        'month'  => 2592000,
        'week'   => 604800,
        'day'    => 86400,
        'hour'   => 3600,
        'minute' => 60,
        'second' => 0
    );
 
    if ($from === null) $from = time();
    $secs = abs($from - $to);
 
    $count = 0;
    $time = '';
 
    foreach ($times as $name => $value) {
        if ($secs >= $value) {
            $slice = floor($secs / $value);
            $time .= $slice . ' ' . $name;
            if (($slice != 1)) $time .= 's';
 
            $secs = $secs % $value;
            $count++;
            if ($count > $granularity - 1 || $secs == 0) break;
            else $time .= ', ';
        }
    }
 
    return $time;
}
 
function phptemplate_node_submitted($node) {
    return t('posted by !username <span title="@long_date">@ago ago</span>',
        array(
            '!username' => theme('username', $node),
            '@long_date' => format_date($node->created, 'long'),
            '@ago' => _fuzzy_delta($node->created)
        )
    );
}
 
function phptemplate_comment_submitted($comment) {
    $node = node_load($comment->nid);
    return t('comment by !username <span title="@date">@later later</span>',
        array(
            '!username' => theme('username', $comment),
            '@date' => format_date($comment->timestamp, 'long'),
            '@later' => _fuzzy_delta($comment->timestamp, $node->created),
        )
    );
}

If you want comments to use "ago" formatting instead of "later", use this version of phptemplate_comment_submitted instead:

function phptemplate_comment_submitted($comment) {
    return t('comment by !username <span title="@date">@ago ago</span>',
        array(
            '!username' => theme('username', $comment),
            '@date' => format_date($comment->timestamp, 'long'),
            '@ago' => _fuzzy_delta($comment->timestamp),
        )
    );
}

Now check out your hot new datestamps!

Note: For my theme, I had to change the comment.tpl.php template file. You might have to change that and/or your node template files. Here's the chunk I had to add to mine:

<div class="submitted"><?php print theme('comment_submitted', $comment); ?></div>

Enjoy.

'Unsupported operand types' in Drupal 6.x

I recently (finally) updated my blog to Drupal 6, but I was having all sorts of trouble running cron. Instead of doing all the chrony magical stuff, it kept saying things like this:

Fatal error: Unsupported operand types in [...]/drupal/includes/common.inc on line 1376
Fatal error: Unsupported operand types in [...]/drupal/includes/common.inc on line 1546

If you run into either of these errors, you've found the one of the joys of a rapidly changing api! As of Drupal 6.x, Drupal has changed the arguments for the l() and url() functions. These functions used to have a bunch of arguments for things like 'absolute', 'query', 'fragment', and so forth. The problem was when you had a fragment but no query string, or you just wanted to declare a url as absolute. You had to add a whole bunch of null arguments to get to the one you actually wanted to set. So Drupal made a wise move and combined all the options into a single argument, fittingly named  $options .

How to fix your unsupported operand type error after the jump.

Command-line batch cron processing for Drupal

In which our hero carries on the time-honored tradition of posting some random snippet to his blog because he might wish to reference it later.

I occasionally need Drupal to execute its cron handler more than once. Today, I was trying to regenerate api documentation for about 18,000 files, which, understandably, is broken up into several cron runs. If I wait for the natural course of things, the documentation will take about three days to regenerate. If I run cron as fast as I can, it usually finishes in less than an hour. So here's the snippet. Paste this bad boy in your favorite shell.

while true; do /usr/bin/wget -O - -q http://example.com/cron.php; done

When you're done, hit ctrl+C to end the loop.

Note that each wget call blocks until it's finished, so you don't get a bunch of wget processes spawned. Basically it ensures that the next cron job is started as soon as possible after the previous one finishes.

multisite drupal problems

I've noticed that the vast majority of problems people have getting multisite Drupal to work comes from not understanding this one concept:

/**
 * The configuration directory will be discovered by stripping the
 * website's hostname from left to right and pathname from right to
 * left. The first configuration file found will be used and any
 * others will be ignored. If no other configuration file is found
 * then the default configuration file at 'sites/default' will be used.
 *
 * For example, for a fictitious site installed at
 * http://www.drupal.org/mysite/test/, the 'settings.php'
 * is searched in the following directories:
 *
 *  1. sites/www.drupal.org.mysite.test
 *  2. sites/drupal.org.mysite.test
 *  3. sites/org.mysite.test
 *
 *  4. sites/www.drupal.org.mysite
 *  5. sites/drupal.org.mysite
 *  6. sites/org.mysite
 *
 *  7. sites/www.drupal.org
 *  8. sites/drupal.org
 *  9. sites/org
 *
 * 10. sites/default
 */

Drupal will guess what website you're looking for based on those rules. It will find the closest config file based on those rules. Your config folder must be named just like one of these. You can't name it "drupal" or "mysite", or Drupal will never find it.

If you're getting errors about database misconfigurations, if you have more than one domain showing the same Drupal site, or if you can't seem to get it installed at all, check the name of your config folder…

Reset menu sort "weight" in Drupal 6

One of the most amazing and annoying things in Drupal 6 is the menu system. Thanks to a bunch of JavaScript magick, you can drag menu items around to reorder them, create submenus, and so forth. Unfortunately, once you've moved a menu item it no longer alphabetizes itself. If you want things to alphabetize themselves again, you are left with two options:

You can manually alphabetize the menu or submenu every time you change things or add a new menu item, forever and ever and ever. If you're not too keen on that idea, you can edit each of the menu items and reset their weight to 0, which makes them alphabetize again.

Neither option is ideal, so I created a third: this bookmarklet will set the weight of all menu items to 0 in one fell swoop. It also works for resetting block sort order, if you wanted to do that for some reason.

grab the bookmarklet after the jump.

jQuery update in a multisite drupal environment

Like most Drupal hackers, I love to hate jQuery. The primary reason for this is that the version of jQuery included in Drupal core is always several versions behind where it should be. Unfortunately updating it is a serious undertaking, usually reserved for the next Drupal version. To get us by between releases, there's a module called jQuery update. It's pretty rad. It replaces the core jQuery dependencies with a newer jQuery version. It has one caveat though:

The tricky step with this module is that you will need to *replace* the jquery.js file in core with the jquery.js file included in the jquery_update directory...

In Drupal, messing with core files is poor form. Not only does it mess up the upgrade path, it causes all sorts of discontent in a multisite environment. And I spend most of my time in a multisite environment.

my fix for jQuery update in multisite drupal after the jump.

a drupal path redirect bookmarklet

i threw together a fancy little javascript bookmarklet to create redirects in drupal, and figured i'd share.

for this to work you need the path redirect and prepopulate modules installed... then simply replace "example.com" in the snippet below with your domain name, and save the link as a browser bookmark.

<a href="javascript:d='example.com/';location.href='http://'+d+'admin/build/path_redirect/new?edit[redirect][redirect]='+encodeURIComponent(location.href.split(/[\?#]/)[0].replace(RegExp('https?://'+d,'ig'),''))+'&amp;edit[redirect][query]='+encodeURIComponent(location.href.indexOf('?')&amp;gt;-1?location.href.split(/[\?#]/)[1]:'')+'&amp;edit[redirect][fragment]='+encodeURIComponent(location.href.indexOf('#')&amp;gt;-1?location.href.split(/#/)[1]:'')+'&amp;edit[path]='+encodeURIComponent(String(''+(window.getSelection?window.getSelection():document.getSelection?document.getSelection():document.selection.createRange().text)).replace(/[^a-zA-Z0-9]+/g,'-').toLowerCase());">new path redirect</a>

now click on it, and it will add a redirect to the current page. if you select any text before clicking the bookmarklet, it will use that text for the redirect path. this bookmarklet supports both internal (drupal) and external paths. it also works with urls containing queries (?foo=bar) and fragments (#baz). tested in ie, firefox and safari.

have fun!

where does the justin come from?

answers to a few questions from my server logs:

  • where does the justin come from?
    i'm from the southeastern end of washington state, but i currently reside in utah county, utah.
  • is your space cooler?
    yes, my space is cooler than yours.
  • how do i carry a loaded gun?
    i wouldn't suggest carrying it in your pants, unless you want to become another strange google search result
  • why don't my msplinks work?
    it could be a couple of things. first, msplinks are intended to save myspace users from spammy sites. if myspace (in their infinite wisdom) decided that the site you're linking to isn't kosher, they could have blocked it. second, myspace seems to be sniffing referrers. this means that if you post a msplinks link on any page other than myspace.com (like my blog, for example), it will be redirected to myspace.com...
  • what is this msplinks.com?
    msplinks.com is an anti-spam attempt by myspace that rewrites any URL posted on myspace into something that looks like http://www.msplinks.com/MDFodHRwOi... i talked a bit more about the effect of msplinks.com on SEO a couple of months ago.
  • have you ever seen a mothball?
    yes.
  • is drupal cron.php secure?
    i wouldn't worry too much about it. cron.php just triggers events for modules you've enabled for your site. it grabs all it's instructions from the database and from php files stored on your server, so there's no way to pass it anything sketchy to execute. if someone messes with cron.php, it will just execute the cron jobs a bit sooner than they would otherwise run.
  • what do you say when you meet someone?
    i usually say "hi" or "good to meet you". but that's pretty region specific. in texas they might say "howdy"...
  • why do i suck with girls?
    i'm not sure of the exact cause, but it might be related to the fact that you go to the internet for your answer.
  • is msplinks a virus?
    nope. see the article i linked above.
  • is drupal secure?
    is anything really secure? drupal has a very active developer community, bugfixes and security patches are released in a timely manner, and most of the security updates i've received since installing were for third-party modules, not for the drupal core. i think drupal's doing alright.

inspired by Philipp Lenssen.

new features! ch-ch-check it!

when i get frustrated or stressed, justin hileman dot info gets upgraded. on that note, my blog has a couple of new features that you prob'ly didn't even notice. most of you won't care, but i'll point 'em out anyway. because i'm a nerd like that.

my space is cooler than your space

if you've visited my myspace profile recently, you've noticed that it doesn't look much like a standard myspace profile. which i'm pretty stoked about, since the hideousness of most myspace profiles approaches obscenity.

one key element in the makeover of my space is the blog. you see, i have way too many blogs already, so i really don't want to post to all of them. and i usually just end up crossposting everything from my regular blog to the myspace blog.

so i replaced it.

drupal dynamically generated MySpace blog replacement

the first generation myspace blog replacement was pretty sweet. i wrote a flash based RSS reader that would check my personal blog for new posts, and display them on the 'space. but the powers that be decided that flash files shouldn't be able to link to offsite pages. so the "read more" links didn't actually work.

so we try it again. enter myspace blog replacement, generation two. this one's really rad.

Give it another go after the jump.

a more secure drupal [multisite] install

I love the Drupal CMS. One of my favorite features of Drupal is the ability to do a multisite install. This site and my other blog, i <3 stella, are hosted on the same box, using the same Drupal install. Several sites can share one codebase. Updates are easily rolled out to every site simultaneously. Overall, it's a wonderful idea. But I have some problems with the implementation...

drupal secure multisite tutorial after the jump.

necessity and innovation

Note: The philosophical musing in this blog post is hidden behind a couple of really nerdy anecdotes. Feel free to skip to the punchline.

A couple of years ago I started writing a CMS. I wasn't happy with any that I had found, and I was convinced that I could do a better job. the first iteration was light and fast, database driven, and extremely customizable. But it still required too much hand coding.

I learned a ton from that attempt, so I decided to give it another go. Version 2.0 would be modular and extensible. I decided to build a module for every feature I wanted but hadn't found in a CMS. I was well on my way to the CMS dreams are made of when my external hard drive bit the big one, leaving me with no backup. Then my laptop was stolen, and my CMS was gone forever. 2.0 never made it to a production server.