Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Nov 12 2011
Nov 12

A good night out at the Eircom spiders for two of the Annertech team this week. After Stella's little arrival came a bit early, a flu-ridden Edward was drafted in to fill the void.

Two of our clients were up for Awards. Trócaire had been nominated for two awards - best charity and best campaign, while runireland.com were in the running for the community award. Trócaire unfortunately left empty-handed this time around, but RunIreland were successful in their category.

It's a first Eircom spider award for Annertech - one of many to come hopefully!

Oct 27 2011
Oct 27

Last Friday, October 21, the Bay Area Drupal Camp (BADCamp) played host to the first ever (or so I've been told) Higher Education Drupal Summit. This all-day event was envisioned by the BADCamp organizers as part of a suite of events to take place the day before the camp sessions.

Drupal PlanetDrupaleducation
Oct 18 2011
Oct 18

I will be presenting "Beginning Git" at 10:30am on this Saturday, 10/22 at the Bay Area Drupal Camp. Ever since I caught wind that Drupal was (finally!) moving off of the archaic CVS version control system, and started planning the Great Git Migration, I took it upon myself to be sure that I learn everything I could about Git.

The time was definitely right for me, in my walk of programming, to start using a version control system. I was writing more and more complex stuff, and I would often start off on a tangent working towards a Really Great Idea, only to determine that it was unfeasible... now what? My file system was rife with files of such creative names as "my_project.backup2.final.final.zip" and other much more useless nomenclature. True, most of my work was done alone, but it would be ridiculous to assume that nobody would ever want to collaborate or even just look at my projects.

Sound familiar?

Well, until recently, the Drupal project was on the CVS version control system. For its time, it was a tremendous program for programmers to collaborate. I'm not really knocking it (much) by saying this, but I know for a fact that I wasn't alone in that it was rather cumbersome to use. And it wasn't much fun to work on one's own computer. You really needed a server to use CVS (since it's a centralized system). Needless to say, it wasn't exactly feasible for my day-to-day workflow.

Enter Git.

Git was like a breath of fresh air. A simple installer on my Mac, and I was Up and Running. I can work on it on my own, I can bring in one or two other collaborators, and I can work together with thousands of Drupal community members. The commands are intuitive, and you can use as few as you can remember, or as many as you need to organize your project.

So, now I want to share the knowledge. I've presented this at Drupal Camp Sacramento Area in May, and I'm looking forward to this encore performance at the largest Drupal Camp ever (so far). If you want to follow along, attached is a pdf of my presentation. I'll link to the video once it gets posted.

Sep 20 2011
Sep 20

A few weeks ago the entire Annertech team left from their respective corners of Ireland to attend DrupalCon London 2011.

I flew Cork to Heathrow and after some initial London transportation difficulties (protip: buses are never direct) I found myself safe in Croydon, wandering around under the always reassuring helium filled Druplicon.


During the conference itself, I found myself running around between the sessions in the main conference building, the core conversation room and the offices hosting the Birds of a Feather.

I particularly enjoyed the core conversations. I found them to be very accessible and it was interesting to hear people's visions for the future direction of Drupal. The build up to the conference was marked with a lot of interesting discussions on some of the challenges facing Drupal. During the conference however, many of these issues were worked out through conversation, collaboration and compromise. The core conversations reflected this. People with different interests and priorities all working together to create useful, robust and beautiful software.

I also really enjoyed the BOFs (Birds of a Feather = a small group having an informal discussion). I attended a half-dozen of these discussions on different aspects of front-end development. BOFs are a great way to have a focused chat about areas of shared interest. If you have not participated in a BOF previously you should definitely add it to your to-do list for the next DrupalCon.

Stella, Alan and I all participated in the code sprint on Friday. I worked with Alan on some of the front-end focused issues for Drupal 8 (in particular some HTML 5 issues). Stella worked on upgrading some of her modules to Drupal 7 (FAQ module now has an RC release!).


In the same way that Drupal is as much a community as it is code, DrupalCon is as much about socializing as it is about the sessions.

It is not everyday that you get to hang out with people who are passionately interested in all the various aspects of web design and development. As such, I always try to take maximum advantage of the opportunity to talk script loading strategy during lunch, debate the render API at dinner and chat browser reflows over drinks.

I might be accused of a (tiny) bit of bias, but my social highlight was the DrupalCon pub/table/trivia (delete as appropriate) quiz organised by Drupal Ireland. All at Annertech were involved in putting it together. The table quiz was a refreshingly analogue event with the use of all new-fangled, will-never-catch-on digital devices banned. Unfortunately, the analogue style meant that I had to score the various teams with nothing but pen and paper. Stella, Dermot and myself were the markers for the evening. We may have been a bit rusty for the first round but we progressively modified and improved our hand-crafted algorithm. By the end we were all pretty exhausted, but our table quiz marking skills were much improved!


So, DrupalCon London: one or two buses may have broken down, the occasional lunch time may have been a bit hectic, the odd wifi connection my have timed-out, but we would not have had it any other way. Big thanks to all involved in putting on a great conference.

See you in Denver 2012!

Sep 14 2011
Sep 14

Ever needed to build a list which "sub-selects", say, 5 items from a given list of categories? This snippet should help.

Assume the following schema…

CREATE TABLE content (
  status INT(11) NOT NULL DEFAULT '1',
  created INT(11) NOT NULL DEFAULT '0',
  KEY content_created (created)


CREATE TABLE content_tags (
  PRIMARY KEY (tid, cid),
  KEY content_id (cid)

Now we can insert some dummy data…

TRUNCATE content;
INSERT INTO content (title, STATUS, created) VALUES
  ('Ut Secundum Modo',             1, UNIX_TIMESTAMP('2011-09-12 12:00:00')),
  ('Quidem Accumsan Facilisis',    1, UNIX_TIMESTAMP('2011-09-14 17:00:00')),
  ('Vel Ut Oppeto Interdico ',     1, UNIX_TIMESTAMP('2011-09-10 09:00:00')),
  ('Iustum Nimis Venio',           1, UNIX_TIMESTAMP('2011-09-11 12:30:00')),
  ('Consequat Defui Verto Macto',  1, UNIX_TIMESTAMP('2011-09-13 19:00:00')),
  ('Quae Natu Facilisis Ille Jus', 1, UNIX_TIMESTAMP('2011-09-09 21:15:00')),
  ('Abico Meus Ullamcorper',       0, UNIX_TIMESTAMP('2011-09-01 00:00:00')),
  ('Ulciscor Antehabeo Gravis',    1, UNIX_TIMESTAMP('2011-09-05 11:00:00'));

TRUNCATE tags;  
INSERT INTO tags (title) VALUES ('alpha'), ('beta'), ('gamma'), ('delta');

TRUNCATE content_tags;
INSERT INTO  content_tags(tid, cid) VALUES
  (1,1), (4,1),
  (2,2), (3,2), (4,2),
  (1,3), (3,3), (4,3),
  (3,4), (4,4),
  (3,6), (4,6),
  (1,7), (4,7),

The TRUNCATE's are only there to ensure these test tables are empty and that the auto incrementing ID's starts from 1.

Now if you run the following query, you will get a list of up to 3 of the most recent posts from each category.

SELECT tag_id, tag_name, content_id, content_title FROM (
      WHEN @id != t.id THEN @row_num := 1
      ELSE @row_num := @row_num + 1
    END AS rownum,
    t.id tag_id,
    t.title tag_name,
    c.id content_id,
    c.title content_title,
    @id := t.id
  FROM tags t
  INNER JOIN content_tags ct ON ct.tid = t.id
  INNER JOIN content c ON c.id = ct.cid
  JOIN (SELECT @id := NULL, @row_num := 0) a
  WHERE c.status = 1
  ORDER BY t.id ASC, c.created DESC
) r
WHERE rownum < 4

This produces…

Note how the beta tag only has 2 items; this is due to the INSERT's above. tag_id tag_name content_id content_title 1 alpha 1 Ut Secundum Modo 1 alpha 3 Vel Ut Oppeto Interdico 1 alpha 8 Ulciscor Antehabeo Gravis 2 beta 2 Quidem Accumsan Facilisis 2 beta 5 Consequat Defui Verto Macto 3 gamma 2 Quidem Accumsan Facilisis 3 gamma 4 Iustum Nimis Venio 3 gamma 3 Vel Ut Oppeto Interdico 4 delta 2 Quidem Accumsan Facilisis 4 delta 1 Ut Secundum Modo 4 delta 3 Vel Ut Oppeto Interdico

How to use in Drupal

The above example could easily be adapted for a Drupal site; to list the 4 most recent items in all terms in a given vocabulary…

SELECT term_id, term_name, node_id, node_title FROM (
      WHEN @id != td.tid THEN @row_num := 1
      ELSE @row_num := @row_num + 1
    END AS rownum,
    td.tid term_id,
    td.name term_name,
    n.nid node_id,
    n.title node_title,
    @id := td.tid
  FROM term_data td
  INNER JOIN term_node tn ON tn.tid = td.tid
  INNER JOIN node n ON n.vid = tn.vid
  JOIN (SELECT @id := NULL, @row_num := 0) a
  WHERE n.status = 1 AND td.vid = 1
  ORDER BY td.tid ASC, n.created DESC
) r
WHERE rownum < 5
Aug 08 2011
Aug 08

At DrupalCon Chicago earlier this year, Stella spoke with Kent Bye from Lullabot. They discussed recent improvements to the Coder module which can help you automate code reviews and upgrade your modules. They also discussed future plans for the module that will increase the number of security checks on Drupal modules.

The interview has now been released as a Drupal Voices podcast.

Jul 08 2011
Jul 08

In 2008 I built my first Drupal site, for a furniture business. The site proved to be a big success and we started some other furniture sites as well later on. I also worked on many other Drupal sites and the web in general hasn't been standing still. So last year we decided it was time for an upgrade. Not as easy as thought with a site that is ranking extremely well for a wide range of keywords. It also appeared more difficult to get to a new design.

I moved the entire teakdump site to teakmeubelen.nl, which is super easy with Aegir, added the new theme, and many of the custom modules I had developed for the new Meubels website. With that most of the site was ready except for some settings that weren't easy to grab in code.

As early adopters of technology (especially within the furniture world) we'd also been entertaining the thought of having a site that works well on mobile phones. I had already seen and heard of Mobile Tools, so I gave it a quick try.

The Drupal modules

I was pleasantly surprised by the integration with Display Suite and Context. That plus the Fusion Mobile theme gave me fairly decent mobile site in a very short time.

The mobile build mode make it a snap to get a slightly more usable rendering of nodes on mobile devices and the mobile context makes it easy to quickly rearrange the entire site for mobile devices.

I noticed an issue though: the Display Suite build mode was showing on the desktop site! I had to disable Mobile Tools and look back into this the next day.

Then I checked WURFL but that seemed to just complicate matters a bit more.

As it seems now there was a little bug in the code that sets the build mode, at least I wrote a patch for the Mobile Tools that works very well for me!

The main other thing I ran into was the caching of the theme. I'm using two different themes, one for the desktop version and one for the mobile version of the site. This doesn't work at all if you have the same URLs. So finally I had to take the redirect out of Aegir and into my nginx configuration so that m.teakmeubelen.nl also points to the same install (without a redirect).


From blog posts I gathered that Google doesn't seem to consider the same content on both desktop and mobile URLs as punishable duplicate content. I just wonder how Google is supposed to detect the mobile URLs (which is one more reason for a nice blog post about my first mobile site).

Now things still need some work here and there but overall I think this site looks great, both on the desktop and on mobiles. And I wonder where to put the iPad and other tablets, as I think the desktop site is just fine on an iPad but Mobile Tools probably considers it mobile hardware anyway.

Jun 19 2011
Jun 19

I've been asked what I meant by "The Drupal Way" in my Drupallets blog post Hiring Drupal professionals, part 1: Know what you need.

In the Drupal community, "Drupal Way" is used in two related, erm, ways. The primary meaning is a general ethos or guiding philosophy, but it also gets used to refer to specific applications of that ethos. In my blog post, I was referencing the general principle, The Drupal Way, as a whole. But google "the drupal way" and you will find lots of hits talking about specific applications of the Drupal way, that is, the Drupal way to do X or the Drupal way to do Y.

The Drupal Way, the ethos, permeates every aspect of Drupal, from coding to content. It is the very essence of the Drupal community. It can be explained many different ways, but I like to boil it down to this:

Don't re-invent the wheel.

Or, put another way:

Share and share alike.

Drupal is not just open source; it is also modular by design. Thus it is not just licensed to share, it is designed to share —easily and flexibly— because by sharing (by not re-inventing the wheel) we all benefit. We can build better, more robust websites more quickly if we take advantage of one another's work than if we do everything ourselves from scratch.

This design and ethos goes beyond the mere existence of contributed modules to add features and functionality. If modules are like Lego bricks, the API(s) are the pegs that let you snap them together firmly to build your creation. Yes, you could lash some bricks together using duct tape, but the result just isn't going to be as good; further, it'll make it harder down the line to make improvements and updates.

Even more fundamental is Drupal's separation of visual appearance (themes) from structure/functionality (core & modules) from site content. Keeping these distinct makes sharing and reusing that much easier. For example, no need to build a whole new site structure and migrate all your content every time you want to totally change the site's visual appearance —just upload a new theme and click a few buttons.

And then there is content. I don't think it is a coincidence that CCK (Content Construction Kit, now in Drupal 7 core as fields) and Views were developed in Drupal. These contributed modules are simply —brilliantly— extensions of The Drupal Way to content. They, and various other Drupal modules as well, make it easy to enter content once but use it many times in many places in many different ways in your site. (For you relational database fans out there: think of a Drupal site as one gorgeous web-enabled relational database for your content. Or, as I heard someone put it: a Drupal site is essentially "things and lists of things". But more on that in another post…)

Finally, it should be noted that The Drupal Way is a guiding ethos, not rigid step-by-step instructions. Not re-inventing the wheel doesn't mean using the exact same wheel for every project. As often as you will hear "There's a module for that…" you will hear "There's different ways you can do that…"

So there you have it: Don't re-invent the wheel. This is the whole Drupal Way. The rest is commentary — and now go learn. (with apologies to Hillel…)

Update: Others' commentary on The Drupal Way

I'll be keeping an ongoing list of other good discussions of The Drupal Way (and/or not re-inventing the wheel). Please post or contact me if you know of any others!

Jun 09 2011
Jun 09

One of my development goals is to learn how to set up continuous integration so that I’ll always remember to run my automated tests. I picked up the inspiration to use Hudson from Stuart Robertson, with whom I had the pleasure of working on a Drupal project before he moved to BMO. He had set up continuous integration testing with Hudson and Selenium on another project he’d worked on, and they completed user acceptance testing without any defects. That’s pretty cool. =)

I’m a big fan of automated testing because I hate doing repetitive work. Automated tests also let me turn software development into a game, with clearly defined goalposts and a way to keep score. Automated tests can be a handy way of creating lots of data so that I can manually test a site set up the way I want it to be. I like doing test-driven development: write the test first, then write the code that passes it.

Testing was even better with Rails. I love the Cucumber testing framework because I could define high-level tests in English. The Drupal equivalent (Drucumber?) isn’t quite there yet. I could actually use Cucumber to test my Drupal site, but it would only be able to test the web interface, not the code, and I like to write unit tests in addition to integration tests. Still, some automated testing is better than no testing, and I’m comfortable creating Simpletest classes.

Jenkins (previously known as Hudson) is a continuous integration server that can build and test your application whenever you change the code. I set it up on my local development image by following Jenkins’ installation instructions. I enabled the Git plugin (Manage Jenkins – Manage Plugins – Available).

Then I set up a project with my local git repository. I started with a placeholder build step of Execute shell and pwd, just to see where I was. When I built the project, Hudson checked out my source code and ran the command. I then went into the Hudson workspace directory, configured my Drupal settings.php to use the database and URL I created for the integration site, and configured permissions and Apache with a name-based virtual host so that I could run web tests.

For build steps, I used Execute shell with the following settings:

mysql -u integration integration < sites/default/files/backup_migrate/scheduled/site-backup.mysql
/var/drush/drush test PopulateTestUsersTest
/var/drush/drush test PopulateTestSessionsTest
/var/drush/drush testre MyProjectName --error-on-fail

This loads the backup file created by Backup and Migrate, sets up my test content, and then uses my custom testre command.

Code below (c) 2011 Sacha Chua ([email protected]), available under GNU General Public License v2.0 (yes, I should submit this as a patch, but there’s a bit of paperwork for direct contributions, and it’s easier to just get my manager’s OK to blog about something…)

// A Drush command callback.
function drush_simpletest_test_regular_expression($test_re='') {
  global $verbose, $color;
  $verbose = is_null(drush_get_option('detail')) ? FALSE : TRUE;
  $color = is_null(drush_get_option('color')) ? FALSE : TRUE;
  $error_on_fail = is_null(drush_get_option('error-on-fail')) ? FALSE : TRUE;
  if (!preg_match("/^\/.*\//", $test_re)) {
    $test_re = "/$test_re/";
  // call this method rather than simpletest_test_get_all() in order to bypass internal cache
  $all_test_classes = simpletest_test_get_all_classes();

  // Check that the test class parameter has been set.
  if (empty($test_re)) {
    drush_print("\nAvailable test groups & classes");
    $current_group = '';
    foreach ($all_test_classes as $class => $details) {
      if (class_exists($class) && method_exists($class, 'getInfo')) {
        $info = call_user_func(array($class, 'getInfo'));
        if ($info['group'] != $current_group) {
          $current_group = $info['group'];
          drush_print('[' . $current_group . ']');
        drush_print("\t" . $class . ' - ' . $info['name']);

  // Find test classes that match
  foreach ($all_test_classes as $class => $details) {
    if (class_exists($class) && method_exists($class, 'getInfo')) {
      if (preg_match($test_re, $class)) {
        $info = call_user_func(array($class, 'getInfo'));
        $matching_classes[$class] = $info;

  // Sort matching classes by weight
  uasort($matching_classes, '_simpletest_drush_compare_weight');

  foreach ($matching_classes as $class => $info) {
    $main_verbose = $verbose;
    $results[$class] = drush_simpletest_run_single_test($class, $error_on_fail);
    $verbose = $main_verbose;

  $failures = $successes = 0;
  foreach ($results as $class => $status) {
    print $status . "\t" . $class . "\n";
    if ($status == 'fail') {
    } else {
  print "Failed: " . $failures . "/" . ($failures + $successes) . "\n";
  print "Succeeded: " . $successes . "/" . ($failures + $successes) . "\n";
  if ($failures > 0) {
    return 1;

I didn’t bother hacking Simpletest output to match the Ant/JUnit output so that Jenkins could understand it better. I just wanted a pass/fail status, as I could always look at the results to find out which test failed.

What does it gain me over running the tests from the command-line? I like having the build history and being able to remember the last successful build.

I’m going to keep this as a local build server instead of setting up a remote continuous integration server on our public machine, because it involves installing quite a number of additional packages. Maybe the other developers might be inspired to set up something similar, though!

2011-06-09 Thu 09:51

Jun 06 2011
Jun 06

(This is the first installment of a multipart series.)

As a Drupal trainer and consultant, I've been getting a lot of phone calls lately either asking if I have trainees to recommend or else hoping that the "consultant" in my job title is a synonym for developer. (It's not: I'm the kind of consultant who helps you figure out what to do rather than the kind that does it for you.) People are having a really hard time finding experienced Drupallers to hire.

At the same time, I've become aware of more and more Drupal projects that went horribly awry because the freelancer or shop hired, though perfectly good PHP coders, didn't really know or understand Drupal. (In just in the last few months, I've personally had not one but two clients who were site-rescue refugees from the same freelancer!)

Unfortunately, the increasing popularity of Drupal can add up to a double whammy for those trying to hire Drupal help. Not only is there a shortage of experienced Drupallers, but there is an increasing number of inexperienced Drupallers offering their services. And these difficulties are compounded by the fact that many of those seeking to hire, quite naturally, don't know very much about Drupal.

So how do you find good Drupallers so your project actually gets the power, flexibility, and ease of use for content creators/managers that led you chose Drupal in the first place?

The first step is to ask for the right thing. There are different kinds of Drupal professionals and most clients and companies seeking to hire Drupallers don't understand or ask for the kind they need.

Besides end users, there are three broad categories of Drupallers: themers, site builders, and module developers. Of these, only module developers actually need to be PHP ninjas

Themers are responsible for the site's graphic design, which in Drupal is independent from the site structure and content. A Drupal site's entire visual design can be completely changed with literally just a couple mouse clicks. (For a demonstration of the independence of content and theme, visit Drupal Gardens —note how the content remains constant as you change from theme to theme.)

Themers come in two flavors: those who customize existing themes (subthemers) & those who create entirely new themes. Only the latter need significant PHP skills, but even then, graphic design and CSS mastery are much more important. For subthemers, PHP doesn't hurt, but isn't vital; often only custom CSS is needed.

Site builders put together the site's structure —the data types, displays, menus, navigation, entry forms, access control, administration, and other functionality for the site's content.

Site builders have even less need for mad PHP skills. Much more important is information architecture, usability, accessibility, and the like. Very complex, feature-rich Drupal sites can be built using only existing core and contributed modules. Indeed, while having familiarity with PHP can help, you actively do not want someone whose first instinct is to code to solve problems. For Drupal, custom PHP should be the last resort, not the first. Re-coding the wheel defeats the purpose and advantage of using Drupal!

If custom PHP is needed for a site —that is, if there is some functionality needed that can't be achieved using existing contributed modules— then it should go in a custom module, which is where module developers come in.

For module developers, of course, PHP is a must. But to be a good module developer, you also need to have good site building skills; you need to understand and appreciate The Drupal Way and also have mastery not just of PHP, but of the Drupal API (and certain contributed module APIs).

I should clarify that there aren't impenetrable divides between these three broad groups. Many themers are also site builders, many site builders are also at least subthemers, etc. and naturally there are various specializations that overlap or even fall somewhat outside these broad three, such as performance optimizers who get into server configuration, etc.

Unfortunately, clients and companies new to Drupal rarely know much about Drupal beyond that it is a CMS based on PHP and databases. So they understandably, but detrimentally, focus on the PHP bit, advertise for "Drupal developers", and emphasize PHP skills in their criteria.

But in a labor market where any kind of experienced Drupaller is in short supply and where there are more good site builders than good module developers, advertising for module developers (which is effectively what advertising for a "Drupal developer" with PHP skills is) when what you need is a good site builder is not the best recipe for success. Good site builders know when to bring in a themer or module developer, but trying to hire a module developer before you even know if you actually need one just frustrates everyone.

Worse, remember those decent PHP coders who don't really know Drupal? They tend to be attracted to "Drupal developer" positions that emphasize PHP skills, too. And that's how sites end up with custom PHP code that is not only totally unnecessary but located in the wrong place (e.g., hacked core/contributed modules or everything in a single theme page.tpl.php file instead of in a custom module where it belongs) and an unsustainable site that doesn't work properly.

You're much more likely to be successful finding and hiring a good Drupal professional if you know what kind you need and ask for it by name. If you're new to Drupal, start by looking for a "Drupal site builder" and emphasize Drupal knowledge and web best practices, not PHP skills. If you already have a site builder but need a graphics professional for your visual design, look for a "Drupal themer" and emphasize graphic design and CSS skills as well as Drupal theming knowledge. If you already have a site builder but determined that existing modules can't do what you need, then it's time to look for a "Drupal module developer" (not just a "Drupal developer") and emphasize Drupal module development knowledge, especially Drupal core and contributed module APIs and best practices.

So, you're advertising/searching for the kind of Drupaller you need, now what? How can you know if you've found them? See the next installment in this series: Hiring Drupal professionals, part 2: Know who they are

[Added 5 Jun 2012] Especially for larger projects, a more expansive discussion of the different roles involved in creating (Drupal) websites can be found in Randall Knutson's great blog post Why Web Development is Like Building a House over at LevelTen. (Just make sure to translate their use of "Developer" to "Site builder"!)

May 28 2011
May 28

Updated! This is the version that I presented at Drupal Camp Sacramento Area at 10 am on 28 May 2011, updated & expanded from the version I presented at DrupalCamp @ Stanford at 10 am on 2 April 2011.

Session description

More than three score and ten useful contributed modules for building Drupal sites.

There are many really useful contributed modules to take your site beyond the basics of Drupal core. There are modules to improve, allow, and/or help with everything from accessibility to workflow, from images to input formats, and beyond.

This session will be of interest to beginner and intermediate Drupallers, as well as those who manage or hire Drupallers or who are just trying to decide whether to use Drupal.

(This functionality is part of core in Drupal 7.) indicates some or all of the module's functionality is part of core in Drupal 7.

May 10 2011
May 10

I've had several people ask me recently how I managed to get the nice date effect on my blog headers. It's quite simple really. All you need is:

  • An Image - A background image (a CSS Sprite) which contains the days, months and years.
  • Some HTML - A VERY basic HTML template.
  • Some CSS - To align the image sections.
  • A PHP Snippet - A Drupal preprocess function.

How to make a Nice Date block

The Image

The image I use here was knocked up in Photoshop. It's just a grid of "output". Everything must be lined up pixel perfect to make the CSS easier to generate.

Dates grid

As you can see, on the left we have the Months (Jan -> Dec), then the Days (01 -> 31) and finally the years. You can also make out the grid layout; months are "2 rows per day" and day is "2 rows per year".

Using CSS, we can specify which "section" of the image appears in the HTML template. This is known as "spriting". It's a technique for clipping bits of a single image. Without the spriting technique, we'd need 12 (months) + 31 (days) + 7 (years) = 50 images!


Using the following simple HTML, we can apply CSS to style appropriately.



In the above, the values in curly braces are variable placeholders which should be replaced by appropriate data. SHORT implies a shortened date (eg, the year 2011 shortens to 11, the month January shortens to 01). LONG is the opposite of SHORT and is only there so that Search Engines and screen readers have some content to use (ie accessibility).

For example, 1st January 2011 would result in:




With CSS disabled (or to a Search Engine), this still reads January 1st 2011.


The following CSS is used to align the sprite images into the HTML above.

.nice_date {

.nice_date .month,
.nice_date .day,
.nice_date .year {
  background:transparent url(i/dates.png) no-repeat;
.nice_date .month { top:5px;  left:0;  width:25px; height:10px; }
.nice_date .day   { top:20px; left:0;  width:25px; height:20px; }
.nice_date .year  { bottom:0; right:0; width:15px; height:40px; }

.nice_date .m-01 { background-position:0     0; }
.nice_date .m-02 { background-position:0  -10px; }
.nice_date .m-03 { background-position:0  -20px; }
.nice_date .m-04 { background-position:0  -30px; }
.nice_date .m-05 { background-position:0  -40px; }
.nice_date .m-06 { background-position:0  -50px; }
.nice_date .m-07 { background-position:0  -60px; }
.nice_date .m-08 { background-position:0  -70px; }
.nice_date .m-09 { background-position:0  -80px; }
.nice_date .m-10 { background-position:0  -90px; }
.nice_date .m-11 { background-position:0 -100px; }
.nice_date .m-12 { background-position:0 -110px; }

.nice_date .d-01 { background-position:-25px      0; }
.nice_date .d-02 { background-position:-25px  -20px; }
.nice_date .d-03 { background-position:-25px  -40px; }
.nice_date .d-04 { background-position:-25px  -60px; }
.nice_date .d-05 { background-position:-25px  -80px; }
.nice_date .d-06 { background-position:-25px -100px; }
.nice_date .d-07 { background-position:-25px -120px; }
.nice_date .d-08 { background-position:-25px -140px; }
.nice_date .d-09 { background-position:-25px -160px; }
.nice_date .d-10 { background-position:-25px -180px; }
.nice_date .d-11 { background-position:-25px -200px; }
.nice_date .d-12 { background-position:-25px -220px; }
.nice_date .d-13 { background-position:-25px -240px; }
.nice_date .d-14 { background-position:-25px -260px; }
.nice_date .d-15 { background-position:-25px -280px; }
.nice_date .d-16 { background-position:-50px      0; }
.nice_date .d-17 { background-position:-50px  -20px; }
.nice_date .d-18 { background-position:-50px  -40px; }
.nice_date .d-19 { background-position:-50px  -60px; }
.nice_date .d-20 { background-position:-50px  -80px; }
.nice_date .d-21 { background-position:-50px -100px; }
.nice_date .d-22 { background-position:-50px -120px; }
.nice_date .d-23 { background-position:-50px -140px; }
.nice_date .d-24 { background-position:-50px -160px; }
.nice_date .d-25 { background-position:-50px -180px; }
.nice_date .d-26 { background-position:-50px -200px; }
.nice_date .d-27 { background-position:-50px -220px; }
.nice_date .d-28 { background-position:-50px -240px; }
.nice_date .d-29 { background-position:-50px -260px; }
.nice_date .d-30 { background-position:-50px -280px; }
.nice_date .d-31 { background-position:-50px -300px; }

.nice_date .y-06 { background-position:-75px      0; }
.nice_date .y-07 { background-position:-75px -040px; }
.nice_date .y-08 { background-position:-75px -080px; }
.nice_date .y-09 { background-position:-75px -120px; }
.nice_date .y-10 { background-position:-75px -160px; }
.nice_date .y-11 { background-position:-75px -200px; }
.nice_date .y-12 { background-position:-75px -240px; }
.nice_date .y-13 { background-position:-75px -280px; }

As you can see, the first part just sets up the element sizes and positions. The last chuck of code defines the background offsets for the sprite.

Note: You may need to adjust bits of this based on your own settings. For example, if you remake the Dates PNG Sprite, you will need to adjust ALL the background positions (unless you keep to the same grid).


The following PHP is used to embed the HTML Template into a Node.

function THEMENAME_preprocess_node(&$vars) {
  $vars['nide_date'] = _THEMENAME_nice_date($vars['created']);
function _THEMENAME_nice_date($timestamp) {
  // Nice Date
  $ys = date('y', $timestamp);
  $yl = date('Y', $timestamp);
  $ms = date('m', $timestamp);
  $ml = date('M', $timestamp);
  $d  = date('d', $timestamp);

  return "




"; }

You've probably guessed, but you should replace THEMENAME with the name of the theme (eg, this theme is currently called "tmj2"). You now have a variable, $nide_date to print into your node.tpl.php.

You can also use the same function in hook_preprocess_comment too, if your site has comments enabled.

Other Tips

  • You could bundle this up into a module to re-use across several sites. The preprocess hooks in Drupal 6+ are accessible from Modules.
  • You could alter the Sprite PNG to use a different font; I used Helvetica to fit with the site's clean/simple font design. Someone like Morten (the King of Denmark) might prefer to remake it using Bello to match his blogs header title.
  • Altering the layout is possible too; maybe you're prefer to the date along the top and the month on the side?

If you implement this on your site, please share your link below! (Note to spammers, my site uses No Follow and I check for link spam, so save us both some time ;-) hehe).

Apr 07 2011
Apr 07

I recently came across LiveReload and was impressed. Actually impressed is an under statement. I was amazed. LiveReload has really improved the way I work with css. As it says on its github page LiveReload is browser extension & a command-line tool that:

  1. Applies CSS and JavaScript file changes without reloading a page.
  2. Automatically reloads a page when any other file changes (html, image, server-side script, etc).

Getting it to work is straightforward. You simply need to download the LiveReload gem and install the extension/plugin in your browser of choice (as long as your browser of choice is Chrome, Safari, or Firefox!). When you have all that set up you can make edits to your files and have the changes display instantly in the browser. Even better if you are doing css and js changes the page will only load the css or js file. As such it is like using firebug to tweak properties except that you can do it in your IDE of choice.

I got this to work fine on Drupal 6 but I ran across a bit of a problem using Drupal 7. I was not sure what the problem was but eventually tracked it down to the fact that LiveReload will not work with css files imported using @import. As anyone who has been using Drupal 7 or following its development knows Drupal 7 uses @import extensively to get round the IE 31 link/style tag limit.

LiveReload has such a positive impact on my workflow that I was not going to let something small like that stop me using it! After a little bit of digging around I came across a solution to the problem.

Thanks to the new hook_css_alter it is easy to change the properties of a css file. So something simple like this

function MYTHEME_css_alter(&$css) { // Alter css to display as link tags. foreach ($css as $key => $value) { $css[$key]['preprocess'] = FALSE; } }

will render css links as a link tag rather than a style tag with @import. Perfect! It works because the default style if you do not preprocess is a
tag. You can dig into to drupal_pre_render_styles to learn about how css files in Drupal 7 are rendered.

Mar 30 2011
Mar 30

I would like to claim an utter hatred of race conditions. This is where code is written in such a way that it doesn’t fully consider the possibility of another thread (e.g. another website hit) or threads occurring concurrently. Consider the following which has been increasingly frustrating me recently:

Drupal stores variables in the ‘variables’ table. It also caches these in the ‘cache’ table so that rather than doing multiple SELECT queries for multiple variables, it simple gets all the variables straight out of the cache table in one SELECT then unserializes them.

cron_semaphore is one of these variables which is created when cron starts, then it deletes it when finishing. If it isn’t deleted it should mean that cron hasn’t finished running yet, so the next time cron tries to run it will quit straight away. But due to a certain race condition it doesn’t always get properly deleted as follows (p2 is an abbreviation for an unrelated process running concurrently, e.g. a visitor to your website):

1, cron starts, cron_semaphore variable inserted (and variables cache is deleted)
2. p2 starts, variables cache is empty so “SELECT * FROM {variables}” then…
3. cron finishes, cron_semaphore variable deleted and the variables cache is cleared
4. … p2 inserts result of “SELECT * FROM {variables}” into cache, but that SELECT was called before cron deleted the variable
5. you now have no mention of cron_semaphore in the variables table, but there it is back in the variables cache!

Consider many visits to your website concurrently and you soon realise this can become a very common occurrence. In fact, this exact problem inflicts us at least a handful of times every day. As a result cron keeps trying to run but immediately quits when it sees the semaphore variable still there. After an hour it finally deletes the semaphore but in the meantime crucial stuff doesn’t get done.

Web applications can quickly become riddled with race conditions such as these. I’ve spotted at least two more in Drupal’s core code in the past. When the ‘bugs’ occur as a result they can be tricky to pin down, appearing to be freakish random occurrences. Worse yet, even when found they can be a royal pain to untangle.

Share this:

Like this:

Like Loading...
Mar 05 2011
Mar 05

DrupalCon Chicago is only a few days away and here at Annertech we're really looking forward to it. Alan and I are both flying out tomorrow and will be there for the week, allowing some extra time for sight-seeing and the code sprint on Friday. Both Alan and I are presenting at the conference. Alan and Randy have a session on Git on Drupal.org: It's easier than you think! and of course, my own one (with my co-presenter Jim Berry) on Coder: Upgrade your modules to Drupal 7.

Other highlights of the trip for me are going to the Welcome Party in the Field Museum, meeting an old colleague of mine from my Doolin Technologies days and, of course, the Drupal Trivia night on Thursday. This is actually an event the Drupal Ireland gang are organising. Many thanks to Conor, Síle and Deirdre for helping Alan and myself put together the questions. Word of warning, there are some tricky ones in there, so don't forget to study! We've even got a few prizes, including some Irish whiskey and Drupal Ireland t-shirts. Also major kudos to Tiffany and the rest of the DrupalCon Chicago team for all their assistance.

So here's to a great DrupalCon - we hope to see you there!

Feb 16 2011
Feb 16

Isn't it a pain when you have dozens of Views setup and they are all marked as "overridden" because you just pulled in an updated feature file from somewhere. Features doesn't always notice when the Views on your site aren't up to date.

The following snippet (which you should use with caution) will batch "delete" (or Revert, once the view is in code) all Views which are marked as Overridden. This took a few seconds to run on our development machine.

$views = views_get_all_views();

foreach ($views as $view) {
  if ($view->disabled) continue;

  if ($view->type == t('Overridden')) {
    views_object_cache_clear('view', $view->name);

I ran this from the Devel PHP page (http://www.example.com/devel/php). It essentially does the same as the view module does when you individually revert views, but this does it without confirming on ALL VIEWS MARKED AS Overridden. I cannot stress enough - use at your own risk, and backup your database first!

Jan 16 2011
Jan 16

So, Drupal 7 is out. Have you heard? It was a bit of a quiet launch really. ;-)

When it came to upgrading by blog, I decided (eventually) that I needed to do a cleanup as the Database had been upgraded from 4.7, to 5 and then to 6 - with many modules added and removed in between. New year, new drupal, new blog (kinda)!

It's also a good excuse to test out that some of the modules I maintain actually work in Drupal 7 (such as Page Title and GlobalRedirect). It's also a kick up the arse to get Relevant Content ported to Drupal 7!


The theme was ported over relatively easily - although it's now using the Boron base theme to make it HTML 5 (another new whizz-bang thing). Mostly it was a matter of remembering to change a lot of instances where a variable got printed out to use the new render() function instead.


The content was easily ported over using the following MySQL:


SELECT n.nid, n.vid, n.type, 'und', n.title, n.uid, n.status, n.created, n.changed, n.comment, n.promote, n.sticky, n.tnid, n.translate
FROM [OLD_DB].node n WHERE n.type IN ('blog', 'page');
INSERT INTO [NEW_DB].node_revision
SELECT nr.nid, nr.vid, nr.uid, nr.title, nr.log, nr.timestamp, n.status, n.comment, n.promote, n.sticky
FROM [OLD_DB].node_revisions nr
INNER JOIN [OLD_DB].node n ON n.vid = nr.vid
WHERE n.type IN ('page', 'blog')

Node body

INSERT INTO [NEW_DB].field_data_body
  'node', n.type, 0, nr.nid, nr.vid, 'und', 0, nr.body, nr.teaser,
  CASE nr.format
    WHEN 3 THEN 'full_html'
    ELSE 'filtered_html'
  END AS format
FROM [OLD_DB].node_revisions nr
INNER JOIN [OLD_DB].node n ON n.vid = nr.vid
WHERE n.type IN ('page', 'blog')
INSERT INTO [NEW_DB].field_revision_body
  'node', n.type, 0, nr.nid, nr.vid, 'und', 0, nr.body, nr.teaser,
  CASE nr.format
    WHEN 3 THEN 'full_html'
    ELSE 'filtered_html'
  END AS format
FROM [OLD_DB].node_revisions nr
INNER JOIN [OLD_DB].node n ON n.vid = nr.vid
WHERE n.type IN ('page', 'blog')


  c.cid, c.pid, c.nid, c.uid, c.subject, c.hostname, c.timestamp, c.timestamp,
  CASE c.status WHEN 0 THEN 1 ELSE 0 END,
  c.thread, c.name, c.mail, c.homepage, 'und'
FROM [OLD_DB].comments c
LEFT JOIN [NEW_DB].comment c2 ON c2.cid = c.cid
INNER JOIN [OLD_DB].node n ON n.nid = c.nid
WHERE n.type IN ('page', 'blog') AND c2.cid IS NULL
INSERT INTO [NEW_DB].field_data_comment_body
  'comment', CONCAT('comment_node_', n.type), 0, c.cid, c.cid, 'und', 0, c.comment,
  CASE c.format
    WHEN 3 THEN 'full_html'
    ELSE 'filtered_html'
FROM [OLD_DB].comments c
LEFT JOIN [NEW_DB].field_data_comment_body c2 ON c2.entity_type = 'comment' AND c2.entity_id = c.cid
INNER JOIN [OLD_DB].node n ON n.nid = c.nid
WHERE n.type IN ('page', 'blog') AND c2.entity_type IS NULL
INSERT INTO [NEW_DB].field_revision_comment_body
  'comment', CONCAT('comment_node_', n.type), 0, c.cid, c.cid, 'und', 0, c.comment,
  CASE c.format
    WHEN 3 THEN 'full_html'
    ELSE 'filtered_html'
FROM [OLD_DB].comments c
LEFT JOIN [NEW_DB].field_revision_comment_body c2 ON c2.entity_type = 'comment' AND c2.entity_id = c.cid
INNER JOIN [OLD_DB].node n ON n.nid = c.nid
WHERE n.type IN ('page', 'blog') AND c2.entity_type IS NULL

URL Aliases

INSERT INTO [NEW_DB].url_alias
SELECT u.pid, u.src, u.dst, 'und'
FROM [OLD_DB].url_alias u
LEFT JOIN [OLD_DB].node n ON u.src = CONCAT('node/', CAST(n.nid AS CHAR))
WHERE (u.src LIKE 'node/%' OR u.src LIKE 'taxonomy/%') AND (n.type IS NULL OR n.type IN ('page', 'blog'))


INSERT INTO [NEW_DB].taxonomy_term_data
SELECT t.tid, t.vid, t.name, t.description, 'full_html', 0
FROM [OLD_DB].term_data t
INSERT INTO [NEW_DB].taxonomy_term_hierarchy
SELECT t.tid, 0
FROM [OLD_DB].term_hierarchy t
INSERT INTO [NEW_DB].taxonomy_index
SELECT n.nid, t.tid, n.sticky, n.created
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].term_node t ON t.nid = n.nid
WHERE n.type IN ('blog', 'page')

Node taxonomy

INSERT INTO [NEW_DB].field_data_field_tags
  'node', i.type, 0 AS deleted, i.nid, i.vid, 'und' AS LANGUAGE,
  @delta := CASE WHEN @prevnid = i.nid THEN @delta:[email protected]+1 ELSE CASE WHEN @prevnid := i.nid THEN @delta := 0 ELSE @delta := 0 END END AS delta,
  SELECT n.nid, n.vid, n.type, t.tid
  FROM [OLD_DB].node n
  INNER JOIN [OLD_DB].term_node t ON t.nid = n.nid
  WHERE n.type IN ('blog', 'page')
  ORDER BY n.nid ASC
) AS i
INSERT INTO [NEW_DB].field_revision_field_tags
  'node', i.type, 0 AS deleted, i.nid, i.vid, 'und' AS LANGUAGE,
  @delta := CASE WHEN @prevnid = i.nid THEN @delta:[email protected]+1 ELSE CASE WHEN @prevnid := i.nid THEN @delta := 0 ELSE @delta := 0 END END AS delta,
  SELECT n.nid, n.vid, n.type, t.tid
  FROM [OLD_DB].node n
  INNER JOIN [OLD_DB].term_node t ON t.nid = n.nid
  WHERE n.type IN ('blog', 'page')
  ORDER BY n.nid ASC
) AS i


INSERT INTO [NEW_DB].file_managed
  f.fid, f.uid,
  SUBSTRING_INDEX(f.filepath, '/', -1) AS `filename`,
  REPLACE(f.filepath, 'sites/thingy-ma-jig.co.uk/files/', 'public://') AS `uri`,
  f.filemime, f.filesize, f.status, f.timestamp
FROM [OLD_DB].files f
INSERT INTO [NEW_DB].field_revision_field_image
  'node', n.type, 0 AS `deleted`, n.nid, n.vid, 'und' AS `language`, 0 AS `delta`, ctb.field_image_fid AS `fid`,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(ctb.field_image_data, LOCATE('alt', ctb.field_image_data)), 8), '"', 2), '"', -1) AS `alt`,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(ctb.field_image_data, LOCATE('title', ctb.field_image_data)), 8), '"', 2), '"', -1) AS `title`
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].content_type_blog ctb ON ctb.vid = n.vid
WHERE n.type IN ('blog') AND ctb.field_image_fid IS NOT NULL

Page Title

INSERT INTO [NEW_DB].page_title
FROM [OLD_DB].page_title p
LEFT JOIN [NEW_DB].page_title p2 ON p.type = p2.type AND p.id = p2.id

Meta Tags

Unfortunately, at the time of writing, the Metatags module is not available and Nodewords has not been updated. The current "hack" solution is to have two fields (field_meta_description and field_meta_keywords), add then to the node and term 'bundles' and just use SQL to get the content into them. Then, using a custom module, embed them into the header manually (using hook_html_head_alter).


INSERT INTO [NEW_DB].field_data_field_meta_description
  'node', n.type, 0, n.nid, n.vid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 5 AND nw.id = n.nid AND nw.name = 'description'
WHERE n.type IN ('blog', 'page');
INSERT INTO [NEW_DB].field_revision_field_meta_description
  'node', n.type, 0, n.nid, n.vid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 5 AND nw.id = n.nid AND nw.name = 'description'
WHERE n.type IN ('blog', 'page');
INSERT INTO [NEW_DB].field_data_field_meta_keywords
  'node', n.TYPE, 0, n.nid, n.vid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 5 AND nw.id = n.nid AND nw.name = 'keywords'
WHERE n.type IN ('blog', 'page');
INSERT INTO [NEW_DB].field_revision_field_meta_keywords
  'node', n.type, 0, n.nid, n.vid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].node n
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 5 AND nw.id = n.nid AND nw.name = 'keywords'
WHERE n.type IN ('blog', 'page');


INSERT INTO [NEW_DB].field_data_field_meta_description
  'taxonomy_term', 'tags', 0, t.tid, t.tid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].term_data t
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 6 AND nw.id = t.tid AND nw.NAME = 'description';
INSERT INTO [NEW_DB].field_revision_field_meta_description
  'taxonomy_term', 'tags', 0, t.tid, t.tid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].term_data t
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 6 AND nw.id = t.tid AND nw.NAME = 'description';
INSERT INTO [NEW_DB].field_data_field_meta_keywords
  'taxonomy_term', 'tags', 0, t.tid, t.tid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].term_data t
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 6 AND nw.id = t.tid AND nw.NAME = 'keywords';
INSERT INTO [NEW_DB].field_revision_field_meta_keywords
  'taxonomy_term', 'tags', 0, t.tid, t.tid, 'und', 0,
  SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTR(SUBSTR(nw.content, LOCATE('value', nw.content)), 8), '"', 2), '"', -1),
FROM [OLD_DB].term_data t
INNER JOIN [OLD_DB].nodewords nw ON nw.type = 6 AND nw.id = t.tid AND nw.NAME = 'keywords';


I also stripped back some functionality to make the site easier to maintain, but pretty much all of what I needed worked directly from checkout. I'm still using Gravatar, Flickr, Views and Panels (I still need to configure the panels).


I found an odd issue with Drupal core. I didn't want my comments to "permalink" to comment URL's - I wanted them to anchor to their point on the page. So I just thought I'd alter the entity info and switch the callback used for the URI. This caused an error where the Comment module had not completely been updated to the new Drupal 7 API. See Issue 1027936.

Also, as mentioned above, Nodewords/Metatags are currently unavailable so I had to "hack" together my own module using hidden fields. Here is the code that enables me to get Meta description and keywords on nodes and terms + frontpage:

function MODULE_html_head_alter(&$head) {
  if (($obj = menu_get_object('node', 1)) || ($obj = menu_get_object('taxonomy_term', 2))) {
    $description = isset($obj->field_meta_description['und'][0]['safe_value']) ? $obj->field_meta_description['und'][0]['safe_value'] : '';
    $keywords    = isset($obj->field_meta_keywords['und'][0]['safe_value']) ? $obj->field_meta_keywords['und'][0]['safe_value'] : '';
  elseif (drupal_is_front_page()) {
    $description = 'FRONTPAGE DESCRIPTION';
    $keywords = 'FRONTPAGE KEYWORDS';

  if (!empty($description)) {
    $head['tmj_tweaks_description'] = array(
      '#type' => 'html_tag',
      '#tag' => 'meta',
      '#attributes' => array('name' => 'description', 'content' => $description),
  if (!empty($keywords)) {
    $head['tmj_tweaks_keywords'] = array(
      '#type' => 'html_tag',
      '#tag' => 'meta',
      '#attributes' => array('name' => 'keywords', 'content' => $keywords),

This just pulls the node or term object from the Menu API, grabs the values from the field (or the hardcoded frontpage values) and pushes them into the head as html tag elements.

During the upgrade I also submitted some patches to the GeSHi module (which powers the code highlighting on this site) and the Gravatar module (which powers the user profile icons on comments).

So far, I'm really liking Drupal 7 - it's shaping up to be a very nice release!

Jan 05 2011
Jan 05

Drupal 7 has finally been released! It took close to three years and lots of effort and contributions from the community to make this happen. This new release sees a whole load of new features and other improvements, from image handling in core to support for the semantic web through RDFa markup. Particular emphasis was placed on the user experience in this release, so both accessibility and the administrative user interface have been vastly improved.

In order to celebrate the release, the Drupal community are hosting release parties worldwide this coming Friday, 7th January. Annertech will be attending the two parties being held in Ireland, one at 7pm in the Longstone Pub, Dublin and the other at the Dew Drop Inn in Galway. Be sure to come along and join us!

Dec 09 2010
Dec 09

Ever wondered if your Christmas Tree is really any good? Well last weekend I decided to make a website for this purpose. It's just a bit of fun really! You login using your Facebook credentials (using Facebook Connect) and can create Chrismas Tree posts by linking to Flickr or Facebook photo's. You can then vote on your favourite tree's in an "A vs B" type match. If you like the sound of this and fancy something a bit festive, please give it a go and let me know if you have any thoughts! Head over to Rate My Christmas Tree :-)

I've published one of the modules this site produced: Media: Facebook. This module allows you to embed a Facebook photo into an EmField by simply copying and pasting the page URL from Facebook. The module then usesthe Facebook API to lookup all available sizes. It is a beta and still needs work, but certainly covers the basics.

I've also written a custom module for comparing and voting between two nodes. I looked into the Voting API (which is AWESOME), btu it only allows you to store a score aginst a single node. I need a system which defined a "left" and "right" node and a "winner". I also looked into the Smackdown module, which looks great too - however that seems to force you to create a "competition" node for every competition. That could get out of handy very quickly (assuming the site gains any kind of traction). So I wrote a module called Compare (which I've not released yet). It will allow you to create "profiles" of votes. Each profile has it's own role-level access control and stores the User ID, Left, Right, Winner and Timestamp of each vote. The vote URL also uses Drupal's private key system to generate secure URL which is limited to only work for a certain period of time. The hash used to secure the URL also stops people changing the left, right or winning Node ID to anything other than that intended. I want to do more work on it before I release it though.

Aug 11 2010
Aug 11

A development virtual machine can be really handy. It gives you a sandbox of sorts where you can feel free to test and experiment knowing that in a worst-case scenario you can just delete the VM and start over. It can also be a great way to practice server configurations and sketch out "real-world" server setups. Here's the process I follow to setup my Ubuntu 10.04 LTS Drupal development VM in VirtualBox. Aside from the VM-specific steps, these instructions should work for a regular Ubuntu server (VM or not).

Download and install Ubuntu 10.04

Note: Unless otherwise specified, all commands below should be run on the guest VM (Ubuntu).

If you haven't do so already, download and install VirtualBox. Then, download the Ubuntu 10.04 LTS disk image to your host computer.

Next, Create a new virtual machine in VirtualBox. Be sure to select type of "Linux" and OS Type of "Ubuntu" and 32 or 64 bit, accordingly.

Start the machine and, when prompted to select a disk drive, click the icon to the side and open the Virtual Media Manager. Add the ISO file you downloaded to the list of disk images and select it as the media to mount. Walk through the install wizard. When asked to install software, don't choose any packages. We'll add them later. When asked to install the GRUB boot loader, go ahead and choose "yes." Once installation is complete, click on the CD icon in the VM window and unmount the disk image, then choose "Continue" to reboot into the Ubuntu server VM.

Update, upgrade, and setup SSH

Before we do anything else, let's get comfortable in the environment. Working in the small shell window that VirtualBox provides isn't ideal, and it'd be nice to be able to do the rest of the setup over SSH in the terminal of our choice. Install the OpenSSH Server package, upgrade existing packages and then shutdown the server (because we'll have likely updated "linux-headers-server" and other core OS packages, and also so we can do the next step).

If you'd like to make things easier on yourself and not have to type "sudo ..." for every command, run sudo -u root -s to become root for the rest of the session. Otherwise, be sure to add "sudo" before each of the follwing commands.

apt-get install openssh-server apt-get update apt-get upgrade apt-get dist-upgrade shutdown -h now

Get in the habit of running apt-get update and apt-get upgrade regularly. It's one way you keep your Ubuntu server patched and secure! apt-get dist-upgrade should be run if you see "The following packages have been kept back..." when you run apt-get upgrade. This means that some packages haven't been upgraded because they involve core, operating system-level updates that require a restart of the machine.

Configure networking on the host machine

On the host machine, run the following commands below. The first line sets up a handy environment variable you can use later. Just replace "Ubuntu 10.04 LTS" with whatever name you gave your VM when setting it up in VirtualBox. The commands below assume you haven't changed your network adapter type in VirtualBox from the default. If you have, you'll likely need to change the device name.

ubuntu_vm="Ubuntu 10.04 LTS" VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/Protocol" TCP VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/GuestPort" 22 VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/HostPort" 2222 VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guesthttp/Protocol" TCP VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guesthttp/GuestPort" 80 VBoxManage setextradata "$ubuntu_vm" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guesthttp/HostPort" 8888

Now, start the VM back up. You should now be able to SSH into your Ubuntu VM as follows:

ssh localhost -p 2222

Install Apache, PHP and MySQL

Now, install some generally useful packages that will be used for the rest of the process.

apt-get install build-essential git-core subversion cvs python-software-properties curl

Before we get to the web server setup, we need to change Ubuntu's package definitions a bit. Ubuntu 10.04 LTS is configured to pull PHP 5.3.x, but many Drupal 6.x contrib modules aren't compatible with PHP 5.3, so we need to downgrade to PHP 5.2.x. We'll follow some instructions from Khalid at 2bits. We'll take his Approach #3 by adding Ralph Janke's PHP 5.2 repository for Lucid and "pinning" the PHP version to 5.2, to make sure we don't inadvertantly upgrade to 5.3 (I've provided the apt preferences file in my Github repository, for easy access).

add-apt-repository ppa:txwikinger/php5.2 wget http://github.com/jrbeeman/drupal-patches/raw/master/ubuntu-10.04-apt-php-prefs.txt -O /etc/apt/preferences.d/php apt-get update

Now, we'll install the web server, PHP packages (including APC), and MySQL. Please note that you'll be prompted for a MySQL root user password. The last command below also disables some unneeded Apache modules and enables a few that are good to have for Drupal.

apt-get install apache2 php5 php5-cli php5-gd php-pear php5-dev apache2-dev apt-get install mysql-server php5-mysql a2dismod cgi autoindex a2enmod deflate expires rewrite vhost_alias pecl install apc

Configure Apache and PHP

Update PHP settings by editing both /etc/php5/apache2/php.ini and /etc/php5/cli/php.ini and setting the variables below as shown:

safe_mode = Off expose_php = Off memory_limit = 128M display_errors = Off log_errors = On
error_log = /var/log/php/php.log ; (or php_cli.log)

Update APC settings by editing /etc/php5/conf.d/apc.ini:

extension=apc.so apc.enabled=1 apc.enable_cli=1 apc.shm_segments=1 apc.shm_size=32 apc.cache_by_default=1 apc.stat=1

Create the error log files:

mkdir /var/log/php touch /var/log/php/php.log touch /var/log/php/php_cli.log chown -R www-data:www-data /var/log/php chmod -R 0755 /var/log/php

Due to the way Drupal handles URLs, in particular how it handles creating a URL to itself, we need to tell Apache to listen on port 8888. This gets around the "HTTP Request Fail" issue in the status report, among other things:

Edit /etc/apache2/ports.conf and add Listen 8888, so that the beginning of the file looks like:

NameVirtualHost *:80 Listen 80 Listen 8888

Install and configure Drush

Setup Drush and Drush Make to be used server-wide. The commands below grab the latest verison as of this writing, but you can check the Drush and Drush Make project pages to see if there are newer releases:

cd /usr/local/src wget http://ftp.drupal.org/files/projects/drush-6.x-3.3.tar.gz wget http://ftp.drupal.org/files/projects/drush_make-6.x-2.0-beta8.tar.gz tar xzf drush-6.x-3.3.tar.gz tar xzf drush_make-6.x-2.0-beta8.tar.gz mkdir /etc/drush cp /usr/local/src/drush/examples/example.drushrc.php /etc/drush/drushrc.php ln -s /usr/local/src/drush/drush /usr/local/bin/drush

Edit /etc/drush/drushrc.php to add Drush Make to include path:

$options['i'] = '/usr/local/src/drush_make';

Install Drupal

Create a database, download, and install Drupal (be sure to put a password in for the MySQL grant statement where appropriate below). I like to keep my sites in sub-directories of /var/www. These commands will prepare Apache for that working setup. I've provided a couple of files that are downloaded with wget to make this simpler, but feel free to modify them along the way if you wish.

mysql -u root -p -e "CREATE DATABASE drupal_dev; GRANT ALL PRIVILEGES ON drupal_dev.* TO [email protected] IDENTIFIED BY 'password'; FLUSH PRIVILEGES;" wget http://github.com/jrbeeman/drush-make/raw/master/drupal-demo.make -O /var/www/drupal-demo.make wget http://github.com/jrbeeman/drupal-patches/raw/master/ubuntu-drupal-demo_apache2-conf -O /etc/apache2/sites-available/drupal-demo a2dissite 000-default a2ensite drupal-demo chown -R www-data:www-data /var/www sudo -u www-data drush make /var/www/drupal-demo.make /var/www/drupal-demo sudo -u www-data cp /var/www/drupal-demo/sites/default/default.settings.php /var/www/drupal-demo/sites/default/settings.php /etc/init.d/apache2 restart

Walk through Drupal's install wizard by visiting http://localhost:8888. Once you're done, run the following Drush command to quickly enable the modules in the demo site:

cd /var/www/drupal-demo drush en -y admin \ content content_copy fieldgroup filefield imagefield nodereference \ number optionwidgets text userreference bulk_export ctools \ ctools_custom_content page_manager views_content path search devel \ features imageapi imageapi_gd imagecache imagecache_ui masquerade \ pathauto skinr strongarm token panels_mini panels_node panels \ jquery_update vertical_tabs views views_bulk_operations views_export \ views_ui fusion_core


You're done! From here, you've got a fully-functional Ubuntu virtual machine designed for developing Drupal sites.


There are some issues to work out and other tasks to add to these instructions. Any feedback on these items would be greatly appreciated.


  • Drupal reports that it can't make HTTP requests, but it can. I believe this is because we're making requests over port 8888, but Drupal thinks it's running on port 80. Not sure how to get around this, but also not sure it's really all that important for development environments.


  • TODO: Install and configure Aegir
  • TODO: Install and configure Varnish
  • TODO: Install and configure Solr


Jul 07 2010
Jul 07

Alfresco wants to be a best-in-class repository for you to build your content-centric applications on top of. Interest in NOSQL repositories seems to be growing, with many large well-known sites choosing non-relational back-ends. Are Alfresco (and, more generally, nearly all ECM and WCM vendors) on a collision course with NOSQL?

First, let’s look at what Alfresco’s been up to lately. Over the last year or so, Alfresco has been shifting to a “we’re for developers” strategy in several ways:

  • Repositioning their Web Content Management offering not as a non-technical end-user tool, but as a tool for web application developers
  • Backing off of their mission to squash Microsoft SharePoint, positioning Alfresco Share instead as “good enough” collaboration. (Remember John Newton’s slide showing Microsoft as the Death Star and Alfresco as the Millenium Falcon? I think Han Solo has decided to take the fight elsewhere.)
  • Making Web Scripts, Surf, and Web Studio part of the Spring Framework.
  • Investing heavily in the Content Management Interoperability Services (CMIS) standard. The investment is far-reaching–Alfresco is an active participant in the OASIS specification itself, has historically been first-to-market with their CMIS implementation, and has multiple participants in CMIS-related open source projects such as Apache Chemistry.

They’ve also been making changes to the core product to make it more scalable (“Internet-scalable” is the stated goal). At a high level, they are disaggregating major Alfresco sub-systems so they can be scaled independently and in some cases removing bottlenecks present in the core infrastructure. Here are a few examples. Some of these are in progress and others are still on the roadmap:

  • Migrating away from Hibernate, which Alfresco Engineers say is currently a limiting factor
  • Switching from “Lucene for everything” to “Lucene for full-text and SQL for metadata search”
  • Making Lucene a separate search server process (presumably clusterable)
  • Making OpenOffice, which is used for document transformations, clusterable
  • Hiring Tom Baeyens (JBoss jBPM founder) and starting the Activiti BPMN project (one of their goals is “cloud scalability from the ground, up”)

So for Alfresco it is all about being an internet-scalable repository that is standards-compliant and has a rich toolset that makes it easy for you to use Alfresco as the back-end of your content-centric applications. Hold that thought for a few minutes while we turn our attention to NOSQL for a moment. Then, like a great rug, I’ll tie the whole room together.

NOSQL Stores

A NOSQL (“Not Only SQL”) store is a repository that does not use a relational database for persistence. There are many different flavors (document-oriented, key-value, tabular), and a number of different implementations. I’ll refer mostly to MongoDB and CouchDB in this post, which are two examples of document-oriented stores. In general, NOSQL stores are:

  • Schema-less. Need to add an “author” field to your “article”? Just add it–it’s as easy as setting a property value. The repository doesn’t care that the other articles in your repository don’t have an author field. The repository doesn’t know what an “article” is, for that matter.
  • Eventually consistent instead of guaranteed consistent. At some point, all replicas in a given cluster will be fully up-to-date. If a replica can’t get up-to-date, it will remove itself from the cluster.
  • Easily replicate-able. It’s very easy to instantiate new server nodes and replicate data between them and, in some cases, to horizontally partition the same database across multiple physical nodes (“sharding”).
  • Extremely scalable. These repositories are built for horizontal scaling so you can add as many nodes as you need. See the previous two points.

NOSQL repositories are used in some extremely large implementations (Digg, Facebook, Twitter, Reddit, Shutterfly, Etsy, Foursquare, etc.) for a variety of purposes. But it’s important to note that you don’t have to be a Facebook or a Twitter to realize benefits from this type of back-end. And, although the examples I’ve listed are all consumer-facing, huge-volume web sites, traditional companies are already using these technologies in-house. I should also note that for some of these projects, scaling down is just as important as scaling up–the CouchDB founders talk about running Couch repositories in browsers, cell phones, or other devices.

If you don’t believe this has application inside the firewall, go back in time to the explosive growth of Lotus Notes and Lotus Domino. The Lotus Notes NSF store has similar characteristics to document-centric NOSQL repositories. In fact, Damien Katz, the founder of CouchDB, used to work for Iris Associates, the creators of Lotus Notes. One of the reasons Notes took off was that business users could create form-based applications without involving IT or DBAs. Notes servers could also replicate with each other which made data highly-available, even on networks with high latency and/or low bandwidth between server nodes.

Alfresco & NOSQL

Unlike a full ECM platform like Alfresco, NOSQL repositories are just that–repositories. Like a relational database, there are client tools, API’s, and drivers to manage the data in a NOSQL repository and perform administrative tasks, but it’s up to you to build the business application around it. Setting up a standalone NOSQL repository for a business user and telling them to start managing their content would be like sticking them in front of MySQL and doing the same. But business apps with NOSQL back-ends are being built. For ECM, projects are already underway that integrate existing platforms with these repositories (See the DrupalCon presentation, “MongoDB – Humongous Drupal“, for one example) and entirely new CMS apps have been built specifically to take advantage of NOSQL repositories.

What about Alfresco? People are using Alfresco and NOSQL repositories together already. Peter Monks, together with others, has created a couple of open source projects that extend Alfresco WCM’s deployment mechanism to use CouchDB and MongoDB as endpoints (here and here).

I recently finished up a project for a Metaversant client in which we used Alfresco DM to create, tag, secure, and route content for approval. Once approved, some custom Java actions deploy metadata to MongoDB and files to buckets on Amazon S3. The front-end presentation tier then queries MongoDB for content chunks and metadata and serves up files directly from Amazon S3 or Amazon’s CloudFront CDN as necessary.

In these examples, Alfresco is essentially being used as a front-end to the NOSQL repository. This gives you the scalability and replication features on the Content Delivery tier with workflow, check-in/check-out, an explicit content model, tagging, versioning, and other typical content management features on the Content Management tier.

But why shouldn’t the Content Management tier benefit from the scalability and replication capabilities of a NOSQL repository? And why can’t a NOSQL repository have an end-user focused user interface with integrated workflow, a form service, and other traditional DM/CMS/WCM functionality? It should, it can and they will. NOSQL-native CMS apps will be developed (some already exist). And existing CMS’s will evolve to take advantage of NOSQL back-ends in some form or fashion, similar to the Drupal-on-Mongo example cited earlier.

What does this mean for Alfresco and ECM architecture in general?

Where does that leave Alfresco? It seems their positioning as a developer-focused, “Internet-scale” repository ultimately leads to them competing directly against NOSQL repositories for certain types of applications. The challenge for Alfresco and other ECM players is whether or not they can achieve the kind of scale and replication capabilities NOSQL repositories offer today before NOSQL can catch up with a new breed of Content Management solutions built expressly for a world in which content is everywhere, user and data volumes are huge and unpredictable, and servers come and go automatically as needed to keep up with demand.

If Alfresco and the overwhelming majority of the rest of today’s CMS vendors are able to meet that challenge with their current relational-backed stores, NOSQL simply becomes an implementation choice for CMS vendors. If, however, it turns out that being backed by a NOSQL repository is a requirement for a modern, Internet-scale CMS, we may see a whole new line-up of players in the CMS space before long.

What do you think? Does the fundamental architecture prevalent in today’s CMS offerings have what it takes to manage the web content in an increasingly cloud-based world? Will we see an explosion of NOSQL-native CMS applications and, if so, will those displace today’s relational vendors or will the two live side-by-side, potentially with buyers not even knowing or caring what choice the vendor has made with regard to how the underlying data is persisted?

Mar 22 2010
Mar 22

A lot of people have been asking for the files we used to integrate Alfresco CMIS with Drupal Open Atrium (See ecmarchitect.com blog post). I’ve happily mailed those to whomever asked. I’ve had the intention of testing them with the latest version, cleaning them up, and putting somewhere more appropriate like the Open Atrium feature server, or at the very least, Google Code or GitHub. But it hasn’t happened yet so I figured I’d make them available here and appeal to the Community to give them a good home.

The zip includes a readme file with (very) rough install/config directions.

Good luck!

Feb 16 2010
Feb 16

Fellow Optaros colleague, Chris Fuller, and I want to present on the Alfresco-Drupal integration at Drupalcon in San Francisco (April 19-21). If you’re interested in Alfresco, Drupal, and CMIS (any or all of the above), please vote for our session.

Oct 13 2009
Oct 13

UPDATE: Screencast now lives here:

[embedded content]

I recorded a quick screencast of a simple integration we did to show Open Atrium leveraging Alfresco as a formal document repository via CMIS. This leverages the CMIS Alfresco module we developed and released on Drupal.org.

As I point out in the screencast, there’s not much to the integration from a technical standpoint. Open Atrium is Drupal and the CMIS module already has a CMIS repository browser. So, all we had to do was expose the module as a “feature”, which is something Open Atrium uses to bundle modules together that create a given chunk of functionality.

Readers familiar with Alfresco Share will instantly recognize the Open Atrium concepts. Instead of “sites” Atrium uses “groups”. Instead of “pages” or “tools”, Atrium uses “features”. The overall purpose, self-provisioned team-based collaboration, is the same and many of the tools/features are the same (blog, calendar, member directory). I’m not advocating using one over the other–as usual, what works best for you depends on a lot of factors. I just thought Atrium provided a nice way to show yet another example of Drupal and Alfresco together (post).

Sep 15 2009
Sep 15

People want intranets that are fun and easy to use, full of compelling content relevant to their job, and enabled with social and community features to help them discover connections with other teams, projects, and colleagues. IT wants something that’s lightweight and flexible enough to respond to the needs of the business that won’t cost a fortune.

That’s why Drupal + Alfresco is a great combination for things like intranets like the one Optaros built for Activision and why we had a record-breaking turnout for the Drupal + Alfresco webinar Chris Fuller and I did today. Thanks to everyone who came and asked good questions. I’ve posted the slides. Alfresco recorded the webinar so they’ll make it available soon, I’m sure. When that happens, I’ll update the post with a link. Until then, enjoy the slides.

[UPDATE: Fixed the slideshare link (thanks, David!) and added the links to the webinar recording below]

1. Streaming recording link:

2. Download recording link:

Apr 28 2009
Apr 28

I’ll be in Chicago tomorrow for the Alfresco Meetup. I’ll be speaking during the Barcamp on Alfresco and Drupal integration with CMIS (module, screencast). I’ll also have the Alfresco-Django integration running on my laptop. I may not have time to show Alfresco-Django during my slot, but I’ll be happy to stick around and do informal demos and talk about either integration if you’re interested because I’d like your feedback on it.

Apr 20 2009
Apr 20

People often need to build a custom user interface on top of the Alfresco repository and I see a lot of people asking general questions about how to do it. There are lots of options to consider. Here are four options for creating a user interface on top of Alfresco, at a high level:

Option 1: Use your favorite programming language and/or framework to talk to Alfresco via REST or Web Services. PHP? Python? Java? Flex? Whatever, it’s up to you. The REST API is nice because if you can’t find a URL that does what you need it to out-of-the-box, you can always roll-your-own with the web script framework. This option offers the most flexibility and creative freedom, but of course you might end up building constructs or components that you may have gotten “for free” from a higher-level framework. Optaros‘ streamlined web client, DoCASU, built on Ext-JS, is one freely-available example of a custom UI on top of Alfresco but there are others.

Option 2: Use Alfresco’s Surf framework. Alfresco’s Surf framework is just that–it’s a framework. Don’t confuse it with Alfresco Share which is a team-centric collaboration client built on top of Surf. And, don’t assume that just because a piece of functionality is in Share it is available to you in the lower-level Surf framework. You may have to do some extra work to get some of the cool stuff in Share to work in your pure Surf app. Also realize that Surf is brand new and still maturing. You’ll be quickly disappointed if you hold it to the same standard as a more widely-used, well-established framework like Seam or Django. Surf is a good option for quick, Alfresco-centric solutions, especially if you think you might want to leverage Alfresco’s browser-based site assembly tool, Web Studio, at some point in the future. (See Do-it-yourself Alfresco Surf Code Camp).

Option 3: Customize the Alfresco “Explorer” web client. There are varying degrees to which you can customize the web client. On one end of the spectrum you’ve got Freemarker “presentation templates” followed closely by XML configuration. On the other end of the spectrum you’ve got more elaborate enhancements you can make using JavaServer Faces (JSF). Customizing the Alfresco Explorer web client should only be considered if you can keep your enhancements to an absolute minimum because:

  1. Alfresco is moving away from JSF in favor of Surf-based clients. The Explorer client will continue to be around, but I wouldn’t expect major efforts to be focused on that client going forward.
  2. JSF-based customizations of the web client can be time-consuming and potentially complex, particularly if you are new to JSF.
  3. For most solutions, you’ll get more customer satisfaction bang out of your coding buck by building a purpose-built, eye-catching, UI designed with your specific use cases in mind than you will by starting with the general-purpose web client and extending from there.

Option 4: Use a portal, community, or WCM platform. This includes PHP-based projects like Drupal (Drupal CMIS Screencast) or Joomla as well as Java-based projects like Liferay and JBoss Portal. This is a good option if you have requirements that match up well with the built-in (or easily added-on) capabilities of those platforms.

It’s worth talking about Java portal servers specifically. I think people are struggling a bit to find The Best Way to integrate Alfresco with a portal. Of course there probably is no single approach that will fit every situation but I think Alfresco (with help from the community) could do more to provide best practices.

Here are the options you have when integrating with a portal:

Portal Option 1: Configure Alfresco to be the replacement JSR-170 repository for the portal. This option seems like more trouble than it is worth. If all you need is what you can get out of JSR-170, you might as well use the already-integrated Jackrabbit repository that most open source portals ship with these days unless you have good reasons not to. I’m open to having my mind changed on this one, but it seems like if you want to use Alfresco and a portal, you’ve got bigger plans that are probably going to require custom portlets anyway.

Portal Option 2: Run Alfresco and the portal in the same JVM (post). This is NOT recommended if you need to scale beyond a small departmental solution and, really, I think with the de-coupling of the web script engine we should consider this one deprecated at this point.

Portal Option 3: Run the Alfresco web script engine and the portal in the same JVM. Like the previous option, this gives you the ability to write web scripts that are wrapped in a portlet but it cuts down on the size of the web app significantly and it frees up your portal to scale independently of the Alfresco repository tier. It’s a fast development cycle once you get it set up. But I haven’t seen great instructions for setting it up yet. Alfresco should document this on their wiki if they are going to support this pattern.

Portal Option 4: Write your own portlets that make services calls. This is the “cleanest” approach because it treats Alfresco like any other back-end you might want to integrate with from the portal. You write custom portlets and have them talk to Alfresco via REST or SOAP. You’ll have to decide how you want to handle authentication with Alfresco.

What about CMIS?

CMIS fits under the “Option 1: Use your favorite programming language” and “Portal Option 4: Write your own portlets” categories. You can make CMIS calls to Alfresco using both REST and SOAP from your own custom code, portlet or otherwise. The nice thing about CMIS is that you can use it to abstract the underlying repository so that (in theory) your front-end code will work with different CMIS-compliant back-ends. Just realize that CMIS isn’t a fully-ratified standard yet and although a CMIS implementation is in the Enterprise version of Alfresco, it isn’t clear to me whether or not you’d be supported if you had a problem. (The last response I saw on this specific question was a Peter Monks tweet saying, “I don’t think so”).

The CMIS standard should be approved by the end-of-the-year and if Alfresco’s past performance is an indicator of the future, they’ll be the first to market with a production-ready, fully-supported CMIS implementation based on the final spec.

Pick your poison

Those are the options as I see them. Each one has trade-offs. Some may become more or less attractive over time as languages, frameworks, and the state of the art evolve. Ultimately, you’re going to have to evaluate which one fits your situation the best. You may have a hard time making a decision, but you have to admit that having to choose from several options is a nice problem to have.

Dec 15 2008
Dec 15

We needed to disable all of Drupal’s CSS files from our theme. Here’s how we did it:

function THEMENAME_preprocess(&$variables) {

  // Get rid of all of Drupal's CSS files
  $css = drupal_add_css();
  foreach ($css['all']['module'] as $file => $status) {
    if (!strstr($file, 'modules/MYMODULE')) {
  $variables['styles'] = drupal_get_css($css);

We also wanted (no *real* need) to use screen.css rather than style.css, so we edited THEMENAME.info to have this:

stylesheets[all][] = reset.css
stylesheets[all][] = screen.css

… and we removed the line for style.css from it.

Finally, as the superuser we went to /admin/build/modules (or /themes, can’t remember now) to refresh the theme cache. We also had to tick to enable the theme at /admin/build/themes as although we’d been using the theme for ages quite fine, it wasn’t actually ticked before.

And hey presto, it worked. Should probably add that it took waaaay too long to do though, so though we’d add this snipped for others to read.

Share this:

Like this:

Like Loading...
Dec 01 2008
Dec 01

If you build quality sites that attract a large number of visitors and interaction there eventually will come a point when you have to start looking for ways to offload your files and bring down your server overhead. I have been looking into the CDN issue off and on for the past 6 months. Recently I decided it was time to get something dialed in and move forward. I wanted something that required the least number of hacks and was easy and scalable. This post isn't meant to be an end all to Drupal and CDNs, but rather just some insight into the way I have tackled this issue for the time being.

There are a number of options to choose from and a lot of different ways to go about it. You could get a new server locally and load balance your stuff, you could get a new server locally and use it as a static file server, you could team up with a big time CDN like Akamai or Limelight and go that route, or you could go the less expensive CDN route with something like Amazon S3.

Note: This article doesn't attempt to explain every little detail of what's going on, but rather act as a guide for a developer to work off.

My Goals

Being somebody that is cost conscience and wanting to always try to get the most bang for my buck the idea of a CDN like Limelight didn't quite seem like an appropriate fit for me. I wanted something a little bit less serious that I could ease into. I don't care about having servers all across the world and getting content to people a few milliseconds faster than other solutions. I just want to take a huge load off my local network and put that load somewhere else. If my site is up and the users are happy, I'm happy.

I also don't care if 100% of my site files are not offloaded to the CDN. If I can take care of 95% of my load and leave 5% on my main box it really doesn't bother me. My main goal is to get rid of the majority of the overhead and keep everything scalable and dynamic with a relatively small code footprint.

Which files to offload

With these things in mind I have basically decided that a good route for me was to mainly take care of my two main sources of files: images and videos. If I really wanted to get hardcore I could also move my CSS and Javascript, but I don't see this as important as taking care of the major problems first.

Take my site www.gunslot.com for example. If you hit the homepage you will see that there are a lot of thumbnails and image requests. Each page probably has at least 20 image requests, some as much as 50. So I would rather take care of these requests first and then I can worry about the 5 or so CSS and JS requests later if I have to.

And then I have the video content which can be large in filesize and taxing on the server. This also had to go.

Local file system

Another thing to keep in mind is that I don't want to make my CDN act as my local file system. I don't want people to upload straight to this and only have this as my main system, I just see too many errors and bugs going this route. I would rather just have everything work normally and smoothly through the default local structure and just copy stuff over to the CDN, which brings up an important point.

Synching your content

Just how should you copy your content over to the CDN? Big time CDNs like Limelight allow HTTP synchronization (which Ted Serbinski talks about in his article) which basically copies your files over automatically. S3 does not offer this type of functionality so you will need to go another route.

You could simply copy them all at once programatically and call it a day. You could also maybe set up a cron and copy any new files every few minutes. But how do you know which files are new? How do you know which files get updated and which don't? You could run some PHP scripts like a cURL or file_get_contents to check the file's last modified time, but this has some big overhead (I tried). So when is the best time to copy the file and how should you do it? You most likely want to get the file over to the CDN as quick as possible, but at the same time you want to make sure if the user updates the file or something is changed your CDN reflects that.

My S3 Solution

I chose to go with a hybrid type approach. I basically send a file over to the S3 every time it is requested, if it is not already there, or if it is newer than the current file.

I have one main routing function that when called will run through the flowchart below and figure out what path to return for any given file, either:

Local: http://www.domain.com/files/full/path/myfile.jpg


S3: http://s3domain.com/files/full/path/myfile.jpg

So basically my flowchart goes something like:

The Technical

S3 Interfacing

So if any of you have messed with S3 you know that you are going to need a PHP class first to put your stuff there. I found a decent class off their forums and went with that (it requires PEAR, unfortunately). At one point I'll probably change this to something better if I find one, but it gets the job done for now.

Filetype check

This is just a quick little function that basically checks if the filetype is listed in an array of allowed filetypes that I have chosen, like: jpg, jpeg, gif, png, flv, mp3, etc.

If file exists

So this is one of the most important steps that I took to make this possible. Rather than actually checking if the file exists on S3 with one of the S3 class functions (slow) or via something like cURL or file_get_contents (slow) I went with a database table on my own server that keeps track of what is on the S3 server (fast). This table keeps track of every filepath and when it was created and changed on S3. I am able to use the changed timestamp from my table to check it against the local file's timestamp (filemtime) to know when the file needs to be updated on S3.

Queuing the file for S3

Originally I didn't queue the file at all and just tried to put the file to S3 every time it was requested - mistake. It obviously took waaaaaaay to long to render my pages with this overhead so I opted for the queue method. I created another database table that keeps track of every file that needs to be put to S3. Basically every time a file doesn't exist on S3 I return the local path for the time being and add this file to the queue.

Putting to S3

As for putting the file to S3 there are a lot of ways to do this and it probably depends a bit on your situation. You could run a cron or some type of rsync if you really wanted to dial it in, but for the time being I am going for a bit more simpler method. I simply run 1 put operation at the end of each page request by the users. This seems to work really well right now and gets the files uploaded pretty much within seconds of when they are queued. At any time I have less than 20 or so files in the queue, and obviously once most of my files are on S3 this doesn't need to run anymore. I run my put function using hook_exit and I pick 1 file each time and get it over to S3.

Routing the files with Drupal

So once you have all this ready to go how do you actually get Drupal to replace the current local paths with the S3 paths? Well, there are a number of ways to do this. If you want to replace all your images, CSS, logos and all that stuff you can patch common.inc, file.inc and theme.inc (maybe more) to run through your routing function. Since I don't care too much about this stuff I skipped this part and decided just to replace imagecache and my videos (for now).


Imagecache comes with a sweet theme_imagecache function that allows you to simply over-ride it in your template.php. You basically just need to tweak the $imagecache_url to run through your routing function and decide which path to return.


If you are using Imagecache for profile pics or nodes you may need to flush the imagecache when these are updated so that your new file will run through the queue and be uploaded to S3. This can be done via some hook_form_alter or hook_nodeapi calls that run the imagecache_image_flush function.


My videos are done through a custom module so I can't really offer any help here. I just plugged my routing function into it.

So that's it!

As you can see for each file request that is going through the routing function it will always check to make sure the newest file is on S3. If it's not it just pulls the local file for the time being. As soon as the S3 file is ready it will start pulling that one. If you do an update it will pull the new local file until the new S3 file is ready again.

That's basically how I'm doing it right now and it's working really well. My server is thanking me and even with fairly high traffic the S3 costs are very reasonable. If anyone has any feedback or tips on how I could make this better lemme know!

 Filed under: Internet / Tech, Drupal, S3, File Server, CDN
Sep 11 2008
Sep 11

Lately, I’ve had various frustrations with Drupal which have moved me away from using it for various things. I’d like to go through where I’ve moved away from Drupal, why I’ve made those changes, and my future Drupal decisions.

WordPress rather than Drupal blog

To begin with, this blog is now on WordPress rather than Drupal – and I have to say that I’m loving it… and so are my non-geeky colleagues. It ticks all the right boxes. Its *really* user friendly. Its much easier to add photos (and videos) to posts. And it’s hardly taken long at all to setup.

So where did Drupal go wrong with this? Well, I guess its the ‘kitchen sink’ approach back-firing. In trying to be something to everyone, Drupal runs the risk of being less than perfect to to any one specific task too. WordPress, on the other hand, has the ability to focus on being the very best blogging software out there, and nothing else gets in the way of that or deters it from its ultimate goal.

But also, we needed to seaparate the business end of our website (the creation of yearbooks, using tens of thousands of nodes) from the nodes and users to do with the blog, partly because around this time every year we flush out the old site and start a new. So we were either going to have a separate Drupal install for the blog or use WordPress. We chose WordPress.

Form theming frustrations

Have a look at the form here and let me know how you’d create it using FAPI (we’re on Drupal 6.4). I’m talking specifically about the theming of the form. Yes, its a very simple form. A search form. But let’s have a look at what’s going on with it and discuss the Drupal way versus the way we ended up doing it.

In FAPI, you’d probably have a ‘textfield’ element for the search box and a ‘submit’ element for the ‘Go’ button. Easy enough, one minute of code. But what about the title ‘Name of your school or group’? Probably a title for the textfield element, no? But then how do we get it centred above both the textfield and the submit button? And what about the text under the two fields? A description, right? Again… how do we get it to appear *exactly* where we want it? The look and feel of the form are to me absolutely crucial. I don’t just want a textfield’s title (with annoying colon after it), a textfield, a description, and then a Go button all one on top of the other.

The Drupal solution? Using a theme hook. We define the form in our implementation of hook_form() and then theme that form separately. The ‘programmer’ cares about the functionality of the form but not the visual design of the form. The ‘designer’ doesn’t care about the functionality and instead works on how it looks. But I’m both the programmer and the designer here, and I want my work to be as easy as possible! So, let’s say i go down this route (I tried, I really did). I need to register my theme function in hook_themes(). Okay, I know why, it saves extra code running on every page load. But its still annoying. Now I create my theme function… urgh. You’ve got to really know your FAPI stuff to get this to work. I try for a while but then I give up. It just feels so messy with some code somewhere, some in another place, and then when I ask my colleague to have a look so he can learn how to do it he’s disgusted… doesn’t know what’s going on… starts bad-mouthing Drupal. So we build our own massively simple FAPI instead in about half an hour that does just what we want it to do.

So now we’re using our own super-basic FAPI for this form. Not all forms, just the ones we want complete control over, visually. Rather than using hook_form() and defining a form array, we just hard-code the HTML for the form. Some of you may be in complete horror now thinking about this but its just by far and away the easiest way to get forms to do exactly what you want. Like a forename and surname field side by side rather than one on top of the other, sharing the title ‘Name’ which is a label for the forename field.

We’re sticking with the idea of a validate() and submit() hook though. I like that one. But we’re doing it slighly differently and more simply, so that any new coders we might hire can quickly and easily pick it up.

Going nodeless

I don’t always like nodes. I really, really don’t. I don’t need revisions, and if I did I’d do them in my own way, just the right way for me, rather than a way that kind of works for everyone but not quite perfectly for anyone in specific. I don’t like the way that as uid=1 I get all the extra bits like ‘Authoring information’ which I never touch. Node hooks and the nodeapi which I once loved are now a higgeldy-piggledy mess that’s a real pain for my new hire. I try to explain to him what’s going on when I save a node. “So this function deals with the submission. But not the core node stuff, Drupal deals with that. And if we want something used for all nodes, we put it in here instead. And we can also override this specific bit here.”… he looks on in amazement, totally baffled by what’s going on and why. It would be so much easier for him (and me) to understand if everything’s just in one place.

So what do we gain from nodes? Umm… not much really. We don’t use contributed modules any more because they never do exactly what I want and always do stuff which I don’t want them doing which just make them less efficient. We put all our code in our own one module instead. A massive, hefty module with a dozen or so include files.

We gain the ability to always do $node->nid and use node_save() and other handy things. But we don’t really need nodes, and it frustrates me having to do the extra INNER JOINs on node_revisions etc. So we’re trialing not using nodes at all for one of our content types – our customers. We just have a simple ‘id’ field now in one single table. We no longer need to INNER JOIN node and node_revisions. We haven’t had any problems so far, but the new hire is finding it much easier to code now, without the ‘baggage’ of Drupal.

The future

Our current plan is to gently migrate away from Drupal, perhaps altogether. We like the idea of building our own framework again, one that does exactly what it needs to do for our site. Its not something we can do overnight. Ours is a yearly cycle, following the academic year, and the current plan is to fork the codebase in around January/February and that would mark the beginning of our own framework if we still feel that way then.

In the meantime, we’ll continue using nodes for most of our content types (if simply because migrating away from them would be a long and arduos task with little reward) and we’ll continue to use FAPI for most our forms. But I see us using our own simple FAPI for more and more forms where we need complete control over them, and I see us extending this FAPI to help us reduce using the same code multiiple times.

I think I still like Drupal. I definitely appreciate the vibrant community. But sometimes I thoroughly hate Drupal and get massively frustrated by it. But I still like it in theory at least. One framework for all my websites. But whilst I just work on one massive website it just has so much less use to me.

You’re more than welcome to urge me to stay with Drupal. In fact, I highly hope someone can manage this. I’ve put a lot of time over the last few years into learning Drupal, and it would be a great waste and a shame to lose all that.

Share this:

Like this:

Like Loading...
Feb 07 2008
Feb 07

I should consider becoming a salesman for Drupal because I am always talking about how good it is. But it really is. If you are not using Drupal you have 2 options:

  1. Get Drupal
  2. Suicide

Although suicide may sound appealing you're probably better off just downloading Drupal. Once you download Drupal here are some key things that make it so powerful:

  1. User Accounts

    Being a programmer myself I can tell you that coding this type of thing is not fun. It requires a lot of form checks and admin pages. It's just not something you wanna do. But Drupal developers do (thanks Dries)!

  2. SEO

    I went from 23 visitors a day to 14,000 a day after switching to Drupal. No, not really but I can tell you that Drupal is very good for SEO. It has the internal link structure dialed in pretty well and each "keyword" goes on it's own page with a title of your choice. Google likes and I like.

  3. Theming

    The theming is broken down very nice in Drupal as it allows you to treat inside "nodes" or articles inside your site different from your "page". It also allows you to theme your comments and sidebars and all that stuff. You can also over-ride functions from modules, but that's a more advanced topic.

  4. Scalable

    Gone are the days of installing the same files over and over again on the same server. With Drupal all you need to do is slap one install up on your server and you're good to go. Any additional sites can access the same files and use the same modules. You can share databases or split a new one off for each site.

  5. Modules

    Drupal has a ton of modules that are available to download at your convenience. These modules cover pretty much anything you could ever think of except it doesn't quite offer the "Facebook Clone" module yet.

  6. Management

    Management is a breeze with Drupal. You can easily view all your posts, users and reports. You can unpublish content and comments if you don't want them to be seen. You can ban users or IPs. You can change the positioning of elements on the site in real-time and lots of other fun things.

  7. Time

    Unless you have at least 5 years of spare time on your hands to dial in your own CMS I recommend you download Drupal.

  8. FREE

    It's freaking free. I've seen companies switch from paid CMS's to Drupal cause they realized sometimes bad decisions can be remedied. I've seen people unload a full set of rounds and hit nothing but air... whoops! Drupal isn't The Matrix (yet).

Research Conclusions


(No seriously, I'm not paid to write this.)

 Filed under: Internet / Tech, CMS, Drupal
Dec 17 2007
Dec 17

Drupal, Joomla and Wordpress are the 3 most popular content management systems according to Alexa and most webmasters. They all have their own blend of features and options that allow for extremely powerful and well-built web sites. With such feature rich systems it is important to understand the different aspects of each one to help with making a better choice as to which CMS fits you.

It should be noted that this chart is written from a web developers perspective and I am fairly experienced with creating sites with a CMS or without. I have used all 3 systems for developing web sites, but strictly stick to Drupal now as it allows me to built the most powerful sites the easiest.

  Drupal Joomla Wordpress Homepage http://www.drupal.org http://www.joomla.com http://www.wordpress.org Example Sites Installation Fairly easy, but requires a bit of technical stuff (will be much easier with version 6) Piece of cake A walk in the park Ease of use Not for the casual user. Drupal is usually more oriented towards developers and webmasters, although it is becoming more and more user friendly with each new version. Fairly easy and straightforward. Very easy to use. Pretty much anybody can use this. Administration Lots of control, but a bit confusing for the newbie Very graphical and intuitive! Very easy to administer the site Themes Decent, but lacking when compared to Joomla and Wordpress Good selection of themes Excellent, large theme selection! Comments Yes Yes Yes SEO
  • Fantastic! Title tags match your article title and clean URLs are easy to add.
  • Content is categorized nicely and eveything is listed as a preview and also has a permalink.
  • Requires OpenSEF or SEO extension.
  • May offer security risks due to SEO changes
Fantastic! - All content has a unique page with an SEO friendly Title and URL. Users Yes - Full user accounts and profiles can be set up. Users can log in and set preferences and settings. Yes Yes, can have multiple users Blogs A bit more cumbersome for blogging, but still has many blogging features and modules. Also, great for allowing more than 1 blog. Yes, you can create blogs easily with Joomla By far the best blogging software. Has support for many blogging features and plugins. RSS Feeds Yes, everything has a feed   Yes, made for RSS! Forums Decent, functional forum, but it doesn't have a traditional type of forum look and feel. It requires heavy theming to get it to feel right SimpleMachines integrates in very well. Yes Ecommerce Contributed ecommerce modules that are dencent, but a bit cumbersome Yes, as extensions Joomla offers ecommerce Yes, with plugins Video Full support for conventional video (.mov, .avi, .mpg) and Flash video (.flv) conversion via FFMPEG. Users can upload videos. No, unless embed YouTube videos No, unless embed YouTube videos Audio Full audio support for uploading of MP3s by users with audio module. Yes, with multimedia extensions Yes Photo Galleries Decent looking photo galleries with the use of the Image module. Gallery extensions allow for image galleries Yes Calendars Decent events and calendars, although they could definitely be improved on.   Yes Web 2.0 Yes! Pure! No, it still has the older content driven aspects and less community focus. Yes, very web 2.0! Permissions Yes, very granular permissions allow for precise control over certain features for each user Lacking Yes Scalability Great for adding new sections and elements to a site. It is very easy to build off the existing codebase. Decent Scalabily Limited scalabilty - much harder to add new sections and elements. Multisite Yes, full multisite integration out of the box! No Yes Categorization Complete control over categorization of content. Can sort and order content virtually any way. Super easy to create pages listing whatever you want. Limiting, only allows for section/category/content structure. Very good categorization via categories and tags Documentation Good documentation with lots of user submitted guides and tutorials. The forums are very friendly with helpful users. Decent, but a bit sparse on certain topics. Large community with lots of active contributors. Good documentation over all important aspects of the system. SSL Yes - SSL is all built in and pretty much transparent. Yes, requires some programming No Standards Yes sir! Close, but not exactly Full standards compliance Learning Curve By far the hardest learning curve. Easier to wrap your head around if you are a developer. Pretty easy learning curve. Lots of features, but pretty easy to follow. Fast and easy! Speed Very efficient, but page loads can take a bit longer as it can be common for many queries to happen with modules and features. Very fast with rendering pages. Can also slow down when features are added. Fast, pages render quickly and efficiently. Theming Complete control over all aspects of HTML. Hard to get used to, but fairly straight-forward once learned. Control over most aspects of the theme, although certain parts are injected in that can not be themed or tweaked. Changes on certain parts of the site may break other parts of the site and theming in general is a lot more buggy. Control over most aspects of HTML. Anti-Spam Features Lots of modules, features and captchas for removing and preventing spam Extensions that help prevent spam Lots of spam features Features Tons of features for blogging, communities, social networking, content management and everything you could ever want for a web site. A lot of good features, but lacking in social and user oriented areas. A lot of blogging features, but not many site features. My opinion My CMS of choice. Great for any type of website and very easy to build off. The Drupal community is very driven and has a great vision of where to take Drupal. Very good for brochure type sites. I guess you could say it is less powerfull than Drupal, but more powerful than Wordpress. It doesn't offer complete control on things which I don't particularily like. It is also hard to build off and is kind of more of a web 1.0 system and not web 2.0. If all I had was a blog and I wasn't too good at programming I would use this for sure. By far the best blogging software and still very good for smaller sites without a user base. Stability Rock solid, although be careful when adding modules, things can go downhill very quickly. Also avoid hacking core code. Very stable. Extremely stable. Database
  • MySQL
  • PostgresSQL
MySQL MySQL Security Great Good Great Programming Language PHP PHP PHP WYSIWYG Editor Yes, but not seamlessly integrated Yes Yes, great integration Page Caching Yes Yes   Integrated Statistics Yes Yes Yes Download Size 728kb 878kb 802kb Sitewide Search Yes Yes Yes

Overall they are all very great systems, but Drupal and Wordpress stand out to me as being at the top of their game. Wordpress is best if you are the only user and are mainly blogging or building a small site. Drupal is the best if you have multiple users and are looking to build out a full web community. Joomla kind of falls in between these as it has many features but is a bit less scalable and less customizable.

Jul 15 2007
Jul 15

Building a Drupal Web Site can be a very scary task to both beginner and experienced web designers. I have been building and theming Drupal web sites for about 2 years now and during this time I have developed my skills and picked up a few things that I would like to share.

It should also be noted, that as with any website, good design is not just entirely graphics and typography and stuff. A lot off good design is a well-planned out site with useful features, lots of content in the right places and things of this nature.

You still have to be a decent designer

If you want your site to look good you still have to be or have a decent designer handling it. That's just the way it is. Although Drupal has many exciting and cutting-edge features, these types of things do not make your site stand out as unique and powerful on their own. They need that special touch of an experienced designer to give it the direction and vision that it deserves.

Try to follow the flow of Drupal

When I first started with Drupal I didn't quite fully understand how everything really worked so I would try to force my way around theming. Rather than utilizing something as simple as comments I would try to build my own type of comments that were more "customized" and more precise to what I was looking for. As I soon realized, this became a huge problem and I started running into bugs and theme problems.

It is a much wiser move to try to understand how everything is working together and make sense of it before trying to force your own way on things. All the unique elements of Drupal offer their own set of strengths and weaknesses. Sometimes it may make sense for one and other times it may make sense for another. With a more complete understanding of all the parts of Drupal it really does help A LOT in theming and site building.

I have assembled a basic run down of some things that I usually think about when I begin theming a Drupal site. Most designers may not think this kind of thing is important and ironically this is also the reason why most Drupal sites lack the professional look that they deserve.

A quick run-down of things to consider when planning a Drupal site

User Pages

It is important to understand that all User pages have their own types of attributes. User pages are NOT nodes and do not allow for a lot of the things that nodes do allow for. If you want to have comments on your users pages you can't. You can't use CCK on your user pages either. If you want to have profiles for your users you may be limited by the default "profile" module. In fact, the default profile module can easily be replaced by CCK. For all of my sites I don't even use the user page as the user's page really. I mainly use the user page as a way to keep account information and stuff, but it makes much more sense to me to have an actual node as a user's profile. This way it is much more community oriented and allows for a lot more. As a node you can now comment on user pages, save them to favorites, search through the content on them, have the same moderation features nodes have and soooo much more. To me, this is the only way to go on a more high-end type site.


Nodes are the heart of Drupal. Pretty much any type of content on a web site should be a node. In my opinion, it is usually best to try to keep each type of content constrained to 1 node. That is, try your best not to have 2 or more pieces of content part of one node. For instance, a lot of the time I see people use CCK to attach images to a page node. This may be good in some cases and a quick fix, but it also may not. What if you want somebody to comment on your image? Since it is actually embedded as being part of your page node, it itself is not a node and does not offer any node features. What if you want to allow users to upload images for this particular node, would they be able to? What if you want to allow more than just 1 image per node? What if you want to allow many different types of attachments to this node? These are all questions that I consider and others should consider as well. In most cases I find it much more beneficial long term to allow each piece of content to be its own node type, including images, video and anything else. These items can then be referenced to other nodes using modules such as CCK's NodeReference and other type relationship modules.

It should also be noted that nodes have a huge list of contributed modules that can become super helpful for the organization, management and usability of nodes.

As I touched on earlier, nodes also allow for commenting so it is a good idea to try to keep anything that you want commented as a node.


Taxonomy is another part of Drupal which should also be understood in order to move a site to the next level. Taxonomy has a few unique features that only apply to it.

The first thing to note is that each taxonomy is basically a category that allows for the categorization of nodes. A "vocabulary" can be created that will hold different options for the user to select when creating a certain type of node. For example if you wanted to select the color for your product node you would create a vocabulary called "color" and assign it to the node type "product". You would then create the options or "terms" for this vocabulary. Such terms would include red, blue, yellow, green, etc. When a user went to create a new node they would now be presented with a Color dropdown that allowed them to select the desired color.

Everything that is part of a taxonomy term also has its own page automatically created that now lists every node that is contained in that term. So for example if you had 3 different nodes assigned to the "red" term on the color vocabulary when you go to the page mysite.com/taxonomy/term/1 (where 1 is the Red term ID) you would see 3 nodes that belong to the red term listed from newest to oldest. Mysite.com/taxonomy/term/2 (where 2 is the Blue term ID) would list all the nodes that belong to the 2 term.


Blocks are a great way to add all the cool little sidebar stuff that should be shown on either the current page or multiple pages. Blocks usually contain stuff like similar links, people also did this, subscribe to this here, top 5 images, most bookmarked and stuff like that.

When I first started out with Drupal I would try too hard to include my own types of blocks in my custom node-x.tpl files. I would create lots of cool little details about the current node in these files. I started to run into problems, however, when I wanted to quickly move this piece of text somewhere else, or even on another site as a different style. It really is important to try to keep the pieces of your site broken down as much as possible.


The tabs are a useful feature that are often overlooked by most newbies to Drupal. These tabs contain both sections and sub-sections for the current page that a user is viewing. For instance when viewing a node the user will be presented with the options for "view", "edit" and whatever else has been added here via modules. These tabs can become super useful and should not be overlooked. A lot of the times certain key features can be lost if tabs are not added to the theme.

CSS is key

Due to the nature of Drupal sites, they are usually very content and text driven. This creates great opportunities for CSS as there are many places to style headings, paragraphs and links.

Make use of the theme() feature

The theme function of Drupal is very cool. It allows any function from a module or core file that uses theme_ in front of it to be over-ridden in your template.php file. So what this means is that you can easily change the way a table, username, breadcrumb, pager or even the way comments appear. It is very handy and should be utilized when necessary. For instance, if I wanted to change the way my items listed with theme('item_list') were themed I would pull open my theme.inc file in the includes folder and search for theme_item_list. In here I would be able to over-ride this function and now add or take away from it.

Views can come in very handy

About a year or 2 ago I used to create custom modules and pages for the sole use of listing nodes of a certain type and of a certain taxonomy. With the release of view.module this is no longer necessary as view does this for you on the fly... and then some! View is very handy for creating lists of nodes with a number of parameters such as comments, node type, number of views, date of creation and so forth. Super awesome and useful!

Nodes have 2 templates, the teaser and the body

I didn't understand this concept for a while so some things were confusing to me. I didn't get why the nodes on my homepage looked different than when I was actually viewing the full node.

Nodes have both a teaser and a body element to them. This can be determined in the $page variable located in the node theme file. So if you were viewing the teaser of a node $page would be set to 0. And if you were viewing the body of a node $page would be set to 1.

So you can do a quick little php conditional on your node.tpl file to theme your teasers different than your nodes:

//theme body
//theme teaser

Use different content types for different types of content and theme them with their own node-type.tpl

If you are going to want different types of contents to look differently then you're going to want to give them their own content type. Each content type can have its own theme file so it makes sense to break up your content types accordingly.

Majority of the work can be done with page.tpl

Page.tpl is roughly about 80-90% of the site. First impressions by users will be made off the page.tpl alone. It is important to focus some effort on the page.tpl. This is where having an experienced designer could really come in handy. I usually like to treat the page.tpl just like I would any other site. I first start out in Photoshop and come up with a layout that fits the goals and intentions of the site. Once I am satisfied with everything I strategically export certain elements from Photoshop and bring them over to CSS and my page.tpl.

Avoid hacking up page.tpl.php - Use modules instead!

I would also like to warn against hacking up the page.tpl file. At times it may become appealing to write a quick if statement to maybe hide a certain part of the page for certain sections. Or it may be appealing to show a registration link for users that haven't registered. While this may be a quick fix and get the job done for the time being, I would advise against it as it will only create more and more problems as you try to keep adding more and more "hacked" features via your page.tpl. The better approach to do this which I now use is writing custom modules for these little features. If you run multiple sites it will also be much more helpful as you can easily use these similar little features between sites.

Over-ride core CSS files if you have to

Core CSS files such as system.css can be over-ridden if necessary. For instance, on my tabs I usually like to theme them differently than the default look so I usually will have my own style.css that cancels out the CSS on system.css. This way I never actually delete the file from core and I still maintain the desired look I want with my tabs.

The default user pages suck

As I hinted at earlier the default user pages are trash. They just are. They are boring and nobody cares about how long you've been a member. They want to see your content and learn about you! As of right now you pretty much will have to do some over-riding of the theme_user_profile to get around this. For more information on this you may want to check out this sweet Drupal article.

Try not to over-complicate things

Sometimes all these different things can get very cumbersome and confusing. Every once in a while you may want to step back and just go with the Drupal flow again. Drupal does have pretty much everything worked out so if you are struggling with something you may not be handling it the way Drupal wants you to. Step back for a little bit and see what other options are available to you. You'll be surprised sometimes to find that what you were looking for was just a click away!

Relax, it's not that bad!

It really isn't. Building and theming a Drupal site shouldn't be too bad with the right people and the right vision. In fact it should be 100% easier than building a site with any other system, including scratch. Drupal is the only way I build sites these days because it is just so powerful and so quick that it's the only thing that makes sense to me. I used to build sites completely from scratch with my own type of CMS and that was just retarded (and not the good kind). Drupal makes you quicker, more stable, more secure and much, much better. Drupal rocks!

 Filed under: Internet / Tech, Drupal, Design
Jun 05 2007
Jun 05

When you first get started with Drupal it can be extremely intimidating as there are many options, modules and things goin on. This guide will help show you some of the most important parts of Drupal that really make it stand above other content management systems.

How to control different parts of the page

Different parts of a page are controlled via different template files. The primary ones you will deal with are:

  • page.tpl.php - Controls the whole page view of the site. Everything that lays out the foundation of your site is done here.
  • node.tpl.php - This controls the look of the nodes, or content of your page. This will control how the text that you create as like a 'page' or 'story' looks.
  • block.tpl.php - This controls the blocks that go on your sidebars. You can style theme here.
  • comment.tpl.php - This controls how the comments look.

All you do is pretty much create a file called something like page.tpl.php and then just fill it out w/ whatever HTML you want and then you can choose from a list of variables to use, such as the page title, the navigation, etc.


It is worth mentioning node.tpl a little more as this is an important one that is used a lot in most cases. Each type of node you create can have its own theme. So you could theme your 'page' nodes to look different from your 'story' nodes. This would be done by simply naming your file node-page.tpl.php or node-story.tpl.php - you just add on -nodetype to the file and this will take precedence over the default node.tpl.php :).

You can override any function that begins with theme_ in your template.php file

I didn't really know this for a while and it is very useful information, especially for people into theming. A lot of repetitive Drupal functions use the format theme_username, or theme_table. If you go through modules you will notice they too have the theme_ format. What this does is allow any part that actually emits HTML or display-oriented parts to run through a Drupal function that first searches your template.php and overrides it with something new. This is instead of modules calling theme_username they would call theme('username'). This is basically the same thing except if called as the latter it will allow your custom template.php file to overwrite the default one.

For example, you can customize the user page by overriding the function theme_user_profile

Say you want to change how the user page looks. You could cruise around your modules and find the function that handles this. (You should be able to open up say user.module and search for 'theme_').

You will come across the function: theme_user_profile which looks like this is the one that handles that.

We would now copy this code and paste it into our template.php page:

function theme_user_profile($account, $fields) {
$output = '

$output .= theme('user_picture', $account);
foreach ($fields as $category => $items) {
if (strlen($category) > 0) {
$output .= '

'. $category .'

$output .= '';
foreach ($items as $item) {
if (isset($item['title'])) {
$output .= ''. $item['title'] .'';
$output .= ''. $item['value'] .'';
$output .= '';
$output .= '';
return $output;

We could now rename it to phptemplate_user_profile and we can tweak it however we like :)

function phptemplate_user_profile($account, $fields) {
$output = 'The profile page for '.$account->name.' is super simple but still badass';
return $output;

That's it! You will now have a custom profile page for every user. The possibilities are endless!

Use a node w/ CCK for pretty much anything you want to store data with

CCK is one of the best modules because it allows users to add any custom fields into any node type. Say you want to add a text field for a city. Boom, done. Now your node would have Title, City and Body during creation. Each one of these fields is also themable via the node.tpl file for the node. They can be accessed like $node->field_city[0]['view']. You can add as many, or as few fields as you would like for each node.

You can share multiple sites on the same Drupal install

This is somewhat advanced but not really. If you have multiple sites it makes sense to share the same modules with all of them. That way if you update core or the modules it will take effect on all the sites using that install. You can also share parts of the databases such as users and stuff, but that does a bit more advanced and isn't always necessary. I do think it is necessary to get all sites using the same Drupal install though. Otherwise you are always having to figure out which modules versions its using, make sure all the files have the current stuff uploaded and it just becomes a hassle.

Don't go through and hack up the core code or modules

When I first started using Drupal I did this. I wanted my site EXACTLY how I wanted it so I would do whatever it took to make that change. In a way this is okay, but just don't expect to have an easy upgrade when a new version of Drupal comes out with new features that you have to have. It will work you bad - it did to me. As you grow with Drupal more you will figure out ways to override certain features and use modules to pretty much do anything you want anyway.

The current system path is always sent via arg(n)

To figure out where a certain page is located it can be useful to call arg(n). For instance if you are viewing node 5 and you wanted to figure out what node you were viewing from a module or theme you would call arg(1) and it would return 5. This is because it goes through the default URL structure and splits each section between '/' up.

For instance, when viewing node 5 arg(0) would return 'node' while arg(1) would return 5. arg(2) would return nothing because there is nothing there. It pretty much takes the default Drupal path, regardless of whether there is a path or not because it is all handles internally.

So say you are editing user 3. The path in the url may look like: www.myurl.com/user/3/edit. The args would be:

  • arg(0) = 'user'
  • arg(1) = 3
  • arg(2) = 'edit'

So say you have all your nodes being re-written as just the tile of the node. So externally to users it would look ike: www.myurl.com/node-name-title-here. Internally, however, it would be:

  • arg(0) = 'node'
  • arg(1) = n (where n = whatever the nid is)

Hope that makes sense.

Hooks are awesome

Hooks are a bit more advanced and used in modules but there are still worth mentioning because they are like the heart of Drupal and its modularity. Pretty much any common or major task that is done in Drupal invokes what is called a "hook" This task could be pretty much anything. Drupal comes with a bunch of default hooks that are used, but you can also create your own.

When a user creates a new account the function hook_user is called. When a user edits a node hook_nodeapi is called. What this means is that anytime something happens in Drupal, it tells your module that it is happening and allows you to do something when this happens also. It's very cool. Say you want to also include new users in another custom table when they sign up. You would name your function something like mymodule_user and you would now be able to include these users in a custom table when they sign up :).

Recommended Modules

This tutorial wouldn't be complete without a list of some very useful modules

  1. CCK - CCK allows you to create custom fields for any node type. These fields can range from simple checkboxes, to full blown AJAX date fields.
  2. Views - Views allows you to create custom pages and blocks that list your content in tons of different ways. Say you want to have the 10 most recent story nodes listed descending by most page views. Done!
  3. Pathauto - Pathauto automatically creates a path for nodes, users and taxonomy upon creation based on settings you have control over.
  4. Webform - Webform allows you to set up any type of form that collects info. It saves the data to the database and sends an email to wherever. Very cool!


I hope this little walk-through will clear up and add some insight into the inner workings of Drupal. Drupal really is a super powerful system and it is so cool to use once you have a better understanding of it. It is the only way to build a web site in my opinion.

 Filed under: Internet / Tech, Drupal
Jan 30 2007
Jan 30

Error message

Deprecated function: The each() function is deprecated. This message will be suppressed on further calls in menu_set_active_trail() (line 2405 of /home1/markspap/public_html/kathy/includes/menu.inc). January 29, 2007

Now that I've worked on a few projects with Drupal v4.7, I have to say that I'm impressed as all get-out with it. Personally, I can't think of any reason not to use it on a majority of projects. But Drupal is my personal preference, and I'm not sure it would be the best one for everyone. Movable Type and WordPress are two of the most popular blogging systems. Although they can be used for other types of sites, they were specifically designed for weblogs. Their functionality and features are limited. But what they do, they do very well. For simple sites or weblogs, MT and WP are fairly quick and easy to set up. On the other hand, Drupal is a powerful publishing platform that can be used for everything from e-commerce sites to portals to intranets. It can also be used for blogging—in fact, multiple user weblogs are a default option. I have to admit that running a one-person blog with Drupal is a bit like going after a gnat with a bazooka. But some people like that kind of firepower. For them, the complexity and difficulty of running Drupal is more than rewarded by its versatility. Indeed, for some Drupaliers, developing the necessary skills is a reward in itself. At present, MT and WP are the "point-and-click instamatics" of weblogging software—Drupal is more for those who have their own darkroom in the basement. And this is unfortunate. Drupal is far superior software, and the lure of cool new features could make developers out of a whole generation of otherwise uninitiated users. In addition, the funding that comes with popularity wouldn't hurt Drupal, either. But Drupal will never be widely used outside the development community until it becomes easy enough to install to appeal to the "point-and-click" crowd. I've heard rumors that more custom distributions like CivicSpace and DrupalED may be coming from Drupal developers. With more preconfigured distributions with very good documentation, Drupal could well give MT and WP a run for their money.

Similar Notes

Nov 15 2006
Nov 15

Tailing my error log, I kept coming across annoying errors like this:

[Mon Nov 13 21:14:49 2006] [error] [client xx.xxx.xxx.xxx] PHP Warning: mysql_real_escape_string() expects parameter 1 to be string, array given in /path/to/drupal/includes/database.mysql.inc on line 350, referer: http://www.example.com/node/1234/edit

No matter how hard I tried, I couldn’t reproduce the errors locally, but somehow real users could create them on the live server. I tried to track down the bug but could only go so far – yes, it was happening when a node was edited, and it was in a database query, but which query? There were too many to look at, so I needed more information.

Because I couldn’t reproduce the bug locally, no amount of dprint_r() and such had any effect. Following some advice in #drupal I explored debug_backtrace() and added its output to a custom error log like this:

function mymodule_error($errno, $errstr, $errfile, $errline, $errcontext) {

if (in_array($errno, array(ERR_LOW, E_NOTICE, E_STRICT))) {
$level = false;
elseif (in_array($errno, array(ERR_MED, E_WARNING))) {
$level = ‘WARNING’;
elseif (in_array($errno, array(ERR_HI, E_ERROR))) {
$level = ‘FATAL’;
else $level = ‘UNKNOWN’;

if ($level) {
// don’t log unimportant errors

$functions = array();
foreach (debug_backtrace() AS $errors) {
$functions[] = $errors[‘function’] . ‘:’ . $errors[‘line’];
$trace = join(‘ < = ', $functions);

error_log($level . ' ' . $errfile . ':' . $errline . '. ' . $errstr . '. (' . $trace . ')');

I put the code in a module file, and it didn’t work… an hour of hair pulling later I found the culprit to be devel.module:

function devel_init() {

… this was undoing my error handling setting, so I had to comment out the restore_error_handler() line and my error handler took centre stage.

With the new code live, I just waited a few hours until the bug showed up again – this time, with extra backtrace information to help me debug:

[Tue Nov 14 18:13:14 2006] [error] [client xx.xxx.xxx.xxx] WARNING /path/to/drupal/includes/database.mysql.inc:350. mysql_real_escape_string() expects parameter 1 to be string, array given. (mymodule_error: < = mysql_real_escape_string:350 <= db_escape_string:152 <= _db_query_callback: <= preg_replace_callback:196 <= db_query:1040 <= my_custom_function:659 <= my_nodetype_update:297 <= node_invoke:494 <= node_save:1876 <= node_form_submit: <= call_user_func_array:206 <= drupal_submit_form:132 <= drupal_get_form:1620 <= node_form:2093 <= node_page: <= call_user_func_array:418 <= menu_execute_active_handler:15), referer: http://www.example.com/node/1234/edit

So now I knew exactly where the bug was coming from. ‘db_query’ was called on line ‘1040’ of my file, and of the several ways to get into the function in which line 1040 resides, I knew it had come form my_custom_function on line 659.

Armed with this extra information, tracking down the bug was a breeze. Stupidly, I’d forgotten to do a regular expression I was supposed to do on a variable before doing a db_query, meaning arrays which were supposed to be left alone were wandering into the query pretending to be strings.

Share this:

Like this:

Like Loading...
Nov 13 2006
Nov 13

In our effort to give something back to Drupal (the fantastic open source content management framework at the core of AllYearbooks), we’re aiming to make a physical yearbook for members of the worldwide Drupal community.

Presuming all goes to plan and enough people join, the idea is to produce physical copies of the yearbook (as well as allowing PDF downloads). These will be provided free to Dries and the top 10 Drupal contributors. It may also be possible for other interested members to buy a copy, though this book isn’t being run as a money-making venture.

If you’re a Drupal developer or involved in Drupal in some other way, join the yearbook now. It only takes a few seconds to join, and you can come back any time before printing to update your yearbook entry and upload some gallery photos for collages and other such pages.

So far 8 Drupalites have joined the yearbook. We’re hoping for at least 100 members for it to be worth printing.

If you have any suggestions, please get in touch.

Share this:

Like this:

Like Loading...


About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web