Aug 29 2008
Nik
Aug 29

Day three already… time flies. Today starts off with a theming talk by my roomie MortenDK – who has apparently recently promoted himself to “King of everything”, which is very humble of him, I’m sure. The Dane managed to cuss his way through ninety minutes of ‘Sex, Drupal & Rock n Roll’ with ease, despite the horrific amounts of heckling from yours truly and Si (LyricNZ).

Next up, I watched William Lawrence with his overview of accessibility in Drupal theming. Some good points here, and answers to questions about screen readers, browser extensions and such. All good!

With it being Friday, we took this opportunity to have a long lunch and went out for Goulash soup, which was grand.

Post luncheon, I attended the Acquia beta test kitchen, where I proceeded to break pretty much everything. I am assured that this is providing them with “useful information”, but I worry that there may have been some fairly colourful language used in reference to certain parts of that session…!

Then followed a quick BoF in the form of a chat about taxonomy and menus, hosted by pwolanin and catch, which proved to be quite useful – lots of stuff about the new hierarchical select module by WimLeers and other titbits like taxonomy_context.module and some potential uses.

The evening takes us to the Taj Mahal indian restaurant, where the service was appalling, but the belly dances were frequent. I hear some people enjoyed that rather more than the meal…

Aug 29 2008
Nik
Aug 29

Thursday, Day two. I didn’t do much today in terms of sessions, but I’d spoken to Kieran Lal of Acquia the day before regarding a voluntary interview… more on that in a tick.

I hear that the testing party went well in the morning, the pancakes weren’t fictitious and lots of people turned up!

Just after lunch, we had the Drupal.org redesign session. Mark Boulton [Design] acknowledges that this particular redesign is potentially the hardest thing he’s ever done, with over 200,000 clients involved in the project – I hope he realises how damn picky we all are!! MortenDK, myself and no doubt several others have put our names down to help out with whatever aspects of the theming that we can.

Next, for me, followed an interview with Leisa, who is the usability expert assigned to interview lots of Drupal.org users prior to the redesign process. I surprised myself with how little I could remember about the various aspects of the front page and most other sections of the site. I genuinely hope that these interviews are going to be useful – I strongly suspect that they will be, as I hear that lots of things were being mentioned repeatedly.

Later that night, we had the welcoming party. You can keep the wine spritzers, thanks – I’ll have a beer. A good time was had by all, after we worked out how to actually get beer. Some pretty good photos from the party can be seen on my Flickr account, in the set tagged DrupalConSzeged2008, with the title DrupalCon Szeged Welcome Party

Aug 27 2008
Nik
Aug 27

Day one of the Drupal Con in Szeged, Hungary. Morten and I made the initial keynote speech by Dries & Co. Well, almost. We were in the door about 4 seconds, there was some clapping, and we left. Couldn’t tell you why we were so late… ;)

Sam Boyer then presented a very decent overview of the Panels module, with some information about the direction of Panels 2 and what’s upcoming in version 3. The key fact that people are most likely hanging on for is the release date for Panels 2 under Drupal 6. Sam seems confident that the answer is something akin to “soon, I promise!” This is a Good Thing. Currently we’re developing a magazine website and using version 6, and really Panels 2 is the last great module that’s not working under that version.

One thing I picked up on was the proposed abstraction of the override functionality (node, taxonomy, etc) into a separate module. This is pretty smart thinking – when I give people a tour of the panels administration interface that powers the Curt Smith website, the override stuff is probably about 40% of the show.

As I write this paragraph, Rasmus Lerdorf is talking about some insane low level PHP optimisation stuff. Some of it I get, some of it is working at a level that is so low that I just can’t afford the brain cells to care about it. He makes a pretty convincing argument for this optimisation though, and points out that you’re basically an idiot if you don’t use opcode caching on your server. Ahem, no comment.

Rasmus also mentions how optimisation can have a positive effect on managing environmental damage – specifically he mentioned the ubiquitous kitten and its untimely demise due to environmental damage. I have taken this concept and run, coming up with the concept of a new unit of measurement for computing – “cycles per kitten”.

Next on the agenda (of most interest to me) is Emma Jane Hogbin’s small business overview, or Wolfgang’s rules module talk. Not sure which yet. More later.

Later: went to Emma Jane’s talk, which was very frank and it was most refreshing to see someone with such vigour and passion talk about her business and practises. Currently listening to ChX talking about menus. I have not a lot to say about that, as I understand very little…

Day one is a great start to an already cool ‘Con, so here’s looking forward to more of the same!

Apr 09 2008
Nik
Apr 09

Just a quick tip for an extra, more accessible theming variable. I have personally found that on nearly all the sites I’ve built, I’ve never had a use for the Mission variable. So it struck me that I could probably use this field to output something else; something relevant to the general workings of the site, for sure though.

So, on this site, I have edited the mission variable and put in the copyright notice that you see at the bottom of the page. I saved the config screen and carried on, thinking that it would just be working – I had tested it out on the front page, and the value was appearing where I had placed the $mission variable in the footer area in my page template. No problems, I thought.

Today, I actually noticed that this was not appearing, and I couldn’t work it out for a while, but I trawled through the phptemplate.engine, and in there is some code that conditionally sets the $mission variable on only the front page – perhaps that’s why it doesn’t get used so much?

Anyway, I opened up the template.php file for my theme, and placed in it the code below, in the _phptemplate_variables bit under case 'page' – see here. Now I have a usable variable across all pages of my site, with the added advantage that this is accessible from the admin interface at “site information”. I guess that the theme settings API in Drupal 6 may alleviate this problem, but for simple things like updating the year (which is contained in my © statement) in D5, this is a potential time saver (and face-saver) for administrators.

<?php
 
// populate the $mission variable on every page so we can use it universally
  // don't check <front>, it's already handled in phptemplate.engine
if (!$vars['is_front']) {
   
$vars['mission'] = filter_xss_admin(theme_get_setting('mission'));
  }
?>
Mar 26 2008
Nik
Mar 26

Today I’m just demonstrating a few simple theme adjustments to comments. Comments in Drupal 5 are “not sexy”, out of the box, so in this short post I’m going to illustrate how to:

  • change the text on “submitted” lines in comments (just a little)
  • add nofollow to username links – unless you’re feeling generous
  • remove the “not verified” marker from anonymous users

<?php
// change the "submitted by" text to "posted by"
// note that you can alter the date display too, by changing the way
// format_date() is called - see http://api.drupal.org/api/function/format_date/5
$vars['submitted'] = t('Posted by !a on @b.',
                     array(
'!a' => theme('username', $vars['comment']),
                    
'@b' => format_date($vars['comment']->timestamp)));
?>

This first snippet belongs in the ‘comment’ section of your _phptemplate_variables function in template.php; I have an example of this here.

Moving on to that “not verified” business, and the question of holding on to your link juice, adding an overridden theme_username() function is the way to go. Here’s some code, which you can also place in your template.php file in your theme. Remember, if you don’t have that file in your theme, you can just create it yourself. I personally recommend Zen as a starting point for theming.

<?php
// we can't use phptemplate_username as this is already declared in that engine
function mytheme_username($object) { // rename according to your theme

  // this basically means "if the user has an account"
 

if ($object->uid && $object->name) {
   
// Shorten the name when it is too long or it will break many tables.
   
if (drupal_strlen($object->name) > 20) {  // obviously you could change this value
     
$name = drupal_substr($object->name, 0, 15) .'...';
    }
    else {
     
$name = $object->name;
    }

    if (

user_access('access user prosites/default/files')) {
     
// we could nofollow the internal links too (authenticated user's pages)
      // but there's not really any point - my site doesn't use membership,
      // so usernames are not highlighted -
      // commented line below would do that, though.
      // $output = l($name, 'user/'. $object->uid,
      //     array('title' => t('View user profile.'), 'rel' => 'nofollow'));
     
$output = l($name, 'user/'. $object->uid,
          array(
'title' => t('View user profile.')));
    }
    else {
     
$output = check_plain($name);
    }
  }
// if we're entering this func, the user is anon (i.e. we want to nofollow them)
 
else if ($object->name) {
    if (
$object->homepage) {
     
// this is where we're nofollow-ing the external links to comment authors' pages
      // we don't really need to use t() here, as rel=nofollow is language independent
     
$output = l($object->name, $object->homepage, array('rel' => 'nofollow'));
    }
    else {
     
$output = check_plain($object->name);
    }
   
// commenting out this line prevents "teh ugly" in the $submitted text
    // $output .= ' ('. t('not verified') .')';
 
}
  else {
   
$output = variable_get('anonymous', t('Anonymous'));
  }
  return
$output;
}
?>

If you’ve got any other simple tips like these, let me know, so I can use & share those too!

Update: This code works fine under Drupal 6 as well – but don’t forget to clear your theme registry when you’ve modified things in your theme!

Feb 28 2008
Nik
Feb 28

This snippet of code gives a brief example of how to rewrite components of the $links variable to make them prettier :) Specifically, here I’m overwriting the link generated by the Forward module. You can see the result below: the little envelope icon labelled “Email”. Normally, this would just say “Forward this page”, which is a bit… well, it could be better. Obviously, it’s nice to be able to change these things to taste.

There are two ways to achieve this result: using theme code in template.php, or inside of a helper module. First, I’ll discuss the module approach.

The helper module method is what I had originally used. It’s a little neater, in that you code it once and forget about it, and it doesn’t clutter up template.php’s _phptemplate_variables function, which can easily become bloated with code.

In the module, I’ve added a function to implement Drupal’s hook_link_alter() function. Here’s the code to do it:

<?php
function mymodule_link_alter(&$node, &$links) {
  foreach (
$links as $module => $link) {   // iterate over the $links array
    //drupal_set_message(print_r($links)); // uncomment to display your $links array

    // check if this element is the forward module's link
   

if ($module == 'forward_links') {
     
$title = t('Email this page to a friend');    // change the title to suit
     
$path = path_to_theme() . '/images/email.png' // make an image path

      // now update the links array
      // set the title to some html of the image and choice of link text
     

$links[$module]['title'] = theme('image', $path, $title, $title) . ' Email'; // let's set some attributes on the link
     
$links[$module]['attributes'] = array(
       
'title' => $title,
       
'class' => 'forward-page',
       
'rel' => 'nofollow',
      );
// this must be set, so that l() interprets the image tag correctly
     
$links[$module]['html'] = TRUE;
    }
  }
}
?>

Ok so really, this ought to be done in the theme layer. Like I said, it’s perhaps not as compact and neat, but here’s the code. It’s mostly the same, but note a couple of additions and changes: firstly, we are not changing $links – this is a pre-rendered string by the time it gets to the template.php. We need to get to the original goodies! Hence, we use $vars[&#039;node&#039;]-&gt;links[module-name][field-name].

Secondly, note that because we have now altered the value of one of the original links’ values, does not mean that the node’s $links is correct. This is the bit that caught me out! We must now regenerate the $links variable using the theme_links() function, as per the last line of code below. This mimics what phptemplate.engine does in core.

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch (
$hook) {
    case
'node':
      foreach (
$vars['node']->links as $module => $link) {
        if (
$module == 'forward_links') {
         
$title = t('Email this page to a friend');
         
$path = path_to_theme() . '/images/email.png';
         
$vars['node']->links[$module]['title'] =
             
theme('image', $path, $title, $title) . ' Email';
         
$vars['node']->links[$module]['attributes'] =
              array(
'title' => $title, 'class' => 'forward-page', 'rel' => 'nofollow');
         
$vars['node']->links[$module]['html'] = TRUE;
        }
      }
     
$vars['links'] = theme('links', $vars['node']->links,
                                           array(
'class' => 'links inline'));
      break;
  }
}
?>

You can achieve this effect for anything that’s in the $links array. On this page (below), you can see the link I’ve described here, another for print-friendly pages and also a themed comment link.

Feb 27 2008
Nik
Feb 27

Currently there are three options for creating error pages in the Drupal system, that I know of. I’m going to show here which I think is the best, for reasons of usability, performance and general webmaster sanity. At the foot of this article, there’s some free code too!

The options:

Drupal’s build in error page support

Drupal provides, out of the box, two fields in the Error Reporting configuration screen. These fields can be set to any internal Drupal path. Usually, they will be set to point the user to a page created specifically for the purpose.

The downside to this is that these will now be nodes in the system, and as such they will show up in popular content lists, site searches and the like. This is clearly not desirable.

Update: I have been made aware of an outstanding issue in Drupal core with error pages. This issue means that a user without “access content” permissions cannot access 403 error pages that are created as nodes. This is true in Drupal 5.x and even 6.1, and is another weak point for this mechanism.

Search404 module

Until very recently I was using search404 but I became less than pleased with the results. To start with, I thought I was aiding usability, but as it transpires… not really. The real killer for me is that search404 often gives me empty search result sets, because the path elements just don’t relate specifically enough to the content.

For instance, the node “/blog/my-drupal-article” will almost certainly contain all the words “my drupal article”, but may not contain the word “blog”, except in the path. This means the search doesn’t catch that article, so you get no results. Given that every 404 page the module generates incurs a DB query automatically, this query is effectively just trash, but cannot be disabled.

Customerror module

Customerror module skirts round the issues of having nodes as error pages. The module makes error handling pages available as custom paths inside Drupal. These aren’t nodes, so we have no issues there.

The configuration screen offers up two textarea fields which will contain the page content to be rendered on each of the 403 and 404 page errors. The key to making this more special than just a plain text or html page is the availability of PHP processing for these fields whilst not requiring nodes for the task.

Ok, so what I’m doing here is recommending customerror as the best choice for this task. That said, let’s throw down some code and make this more useful.

To start, visit the standard Drupal error reporting page at “/admin/settings/error-reporting”. Here, set the default error page fields to “customerror/403” and “customerror/404” respectively, if you’re going to override both these pages.

Now, on the Custom Error module’s config page at “/admin/settings/customerror”, enable both checkboxes that say “Allow PHP code to be executed for 40x”. Now let’s look at handling the 404 error. I’ve added the following code for this site, in the “Description for 404” textarea, and a suitably snappy title in the other field: “404 Not Found Error: No content found at the requested URL”.

<p>Sorry, no content was found at the requested path - it's possible that you've requested this page in error.</p>

<p>Use the search form below, or go to the <a href="http://www.kinetasystems.com/">home page.</a></p>

<?php
// check that the search module exists and the user has permission to hit the form
if (module_exists('search') && user_access('search content')) {
 
// cool! - customerror doesn't trash the page request and the full path is available
 
$path = $_REQUEST['destination'];
 
// bin anything that's not alphanumeric and replace with spaces
 
$keys = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', ' ', $path));

  // retrieve the search form using the data we've pull from the request
  // note that we can override the label for the search terms field here too
 
print drupal_get_form('search_form', NULL, $keys, 'node', 'Search terms');
}
?>

In the 403 error fields, we adopt a similar technique. I’ve used “403 Forbidden Error: Access to this page is denied” for the title. Here we display different content depending on whether or not the user is logged in. If you’re running a site with lots of members, you can uncomment the user login line towards the bottom and the login form will be rendered on the 403 page!

<?php global $user; ?>
<?php if ($user->uid): ?> 
  <p>Sorry <?php print $user->name; ?>, you don't have permission to view the page you've just tried to access.</p>
  <p>If you feel that you have received this message in error, please
    <a href="http://www.kinetasystems.com/blog/creating-custom-error-pages-in-drupal/contact">contact us</a> with specific details so that we may review your access to this web site.</p>
  <p>Thanks</p>
<?php else: ?>
  <p>This page may be available to clients and registered users only. Please select from one of the other options available to you below.</p>
  <ul>
    <li><a href="http://www.kinetasystems.com/user/login?<?php print drupal_get_destination(); ?>">Login</a> to view this page</li>
    <li>Use the <a href="http://www.kinetasystems.com/blog/creating-custom-error-pages-in-drupal/search">search</a> facility</li>
    <li>Go to the <a href="http://www.kinetasystems.com/">home page</a></li>
    <li>Go to the <a href="http://www.kinetasystems.com/blog/creating-custom-error-pages-in-drupal/sitemap">site map</a></li>
  </ul>
<?php //print drupal_get_form('user_login'); ?>
<?php endif; ?>

Now we’ve got friendly, usable error pages that are helpful and don’t scare off visitors!

Updated 24th April 2008

Feb 23 2008
Nik
Feb 23

Quite frequently I’ve been asked about putting images into site “sections”, depending on path or menu trail. Look up, that “Blog” image is what I’m talking about. It’s on all blog related pages. So, here goes – it’s nice to be able to finally offer this information here.

The first main chunk of code attempts to get a menu item and build an image link from that. The second chunk assumes failure of the first and tries again using a partial path method.

If all nodes on your site have menu entries, you can use that piece of code independently. Likewise, if all your nodes can be identified by the first bit of the path, the second chunk will stand alone.

I have got a mixture of the two on this site. A lot of the entries have menu entries, but the blog and portfolio section do not. Therefore, the image links on those sections are powered by the second chunk.

Note: this code expects to find sites/default/files of the GIF type in a directory ‘images/sections’ within my own theme directory. It also will only pick up sites/default/files that have names which are all lower case. In the case of menu entries that contain spaces, those will be replaced with hyphens, so if the menu link is “Site Map”, the image name will have to be “site-map.gif”. Path-based is really dependant on how you are using aliases (e.g. your pathauto.module setup) and isn’t really inside the scope of this article. You’ll have to figure that out yourself.

Okay; in order to not crowd up _phptemplate_variables(), I add just this one line of code in template.php inside that function (under ‘page’ – see here for details):

<?php
$vars
['section_link'] = get_section_link();
?>

Then, elsewhere in that file, this code:

<?php
function get_section_link() {
 
// MENU - attempt to make a section link from a menu item, for this page
  // get active menu trail into an array
 
$menu_items = _menu_get_active_trail(); // $menu_items[1] is the top parent of our menu container, e.g. primary links
  // this gets the required menu item into an array
 
$link_array = menu_item_link($menu_items[1], FALSE); // whip out spaces and make the name lower case
 
$section_name = strtolower($link_array['title']);
 
$section_name = str_replace(' ', '-', $section_name);

  if (

$section_link = render_link($section_name)) {
    return
$section_link;
  }
// PATH - if we've not returned, we couldn't make a valid link from menu
  // let's try a path approach instead?
 
if (module_exists('path')) { // dependency for drupal_get_path_alias $sections = array(); // an empty array to collect stuff in

    // get all the top level links in the primary nav (id of 2) into a array
   

$primary_nav = menu_primary_links(1, 2); // iterate over the array and pull out the top level paths
   
foreach ($primary_nav as $menu) { // get the first element of the aliased path for this menu item
     
$path_element = explode('/', drupal_get_path_alias($menu['href'])); // put the first chunk of each path onto an array
     
$sections[] = $path_element[0];
    }
// get the aliased path for the page we're on
   
$section = explode('/', drupal_get_path_alias($_GET['q']));
   
$section_name = $section[0]; // if the path matches a nav item, create a section image
   
if (in_array($section_name, $sections)) {
      if (
$section_link = render_link($section_name)) {
        return
$section_link;
      }
    }
  }
}

function

render_link($section_name) {
 
// construct the image's path (mine are GIFs stored in a subdir of my theme)
 
$image_path = path_to_theme() . '/images/sections/' . $section_name . '.gif'; // make some text for the image's alt & title tags (SEO, accessibility)
 
$image_alt = $section_name . t( ' section');
 
$image_title = $section_name . t( ' section link'); // render image html using theme_image (returns NULL if file doesn't exist)
 
$section_image = theme('image', $image_path, $image_alt, $image_title); // if the image rendered ok, render link using above variables
 
return ($section_image) ? l($section_image, $link_array['href'],
      array(
'title' => $image_title), NULL, NULL, FALSE, TRUE) : NULL;
}
?>

Then finally in page.tpl.php (and any other page templates) we can use the variable in the “Drupal Way”, and print our variable where we like!

<?php if ($section_link): ?>
  <div id="sectionTitle">
    <?php print $section_link; ?>
  </div>
<?php endif; ?>

Feb 14 2008
Nik
Feb 14

Yesterday afternoon, I popped my head onto the Drupal IRC channels, and suddenly everything went a bit strange…

Somebody shouted, “Drupal 6 is out!” – and then the whole place erupted into hysterical blogging and Digg-ing (over 1000 now) and general chaos. Millions of tiny blue aliens are unleashed – Drupal 6 is ALIVE!

But the real story is obviously the news of the latest release of our favourite content management system-stroke-framework! Or should that be “community site building system” or “social publishing platform” or… well. That’s the great thing about Drupal, it can do pretty much anything! Expect it to be doing the washing-up for you by v7.

So what’s under the hoody? More features! Even better security! Massively improved language support! Performance streamlining! Usability enhancements up the wazoo! Just… loads of stuff. Loads.

Please Digg the story if you can, or for a clearer idea of what’s going on, check the official Drupal.org thread on the release here. As if it wasn’t crystal clear already…

Oh, and here is the official Drupal 6 press release. I quite like this doc – cos I helped write it! I wrote the intro text and title (with some help from Keith and final mods). That’s my fifteen nanoseconds of fame…

Feb 11 2008
Nik
Feb 11

I sometimes run into people on the Drupal IRC channels that have a theming issue they just can’t fathom – dysfunctional CSS. Some poor guy has overridden a core CSS class, but his styles just don’t work. I’ve been there myself before and I know it can be very frustrating.

Rather than showering your CSS with !important tags, here is an alternative – remove the offending style file altogether. You can then copy the style information into your own theme, remove any bits that you don’t want and alter it as you see fit.

Let’s see how we get rid of those sites/default/files and regain control of our CSS. Put some code similar to this in the “page” section of your template.php file’s _phptemplate_variables function (see this example).

<?php
// get all the current css information into an array
$css = drupal_add_css();// copy stuff you want to keep from these sites/default/files into your theme's style.css
// or maybe make a separate file for that and @import it into that file

// now we can ditch unwanted core css sites/default/files from the array and they won't be included

unset($css['all']['module']['modules/user/user.css']);
unset(
$css['all']['module']['modules/node/node.css']);// and now, removing the css sites/default/files of some contributed modules
// I'm putting them into an array to save space and code repetition
$rm[] = drupal_get_path('module','content').'/content.css';
$rm[] = drupal_get_path('module','devel').'/devel.css';
$rm[] = drupal_get_path('module','gotcha').'/gotcha.css';// now we can remove the contribs from the array
foreach ($rm as $key => $value) {
  unset(
$css['all']['module'][$value]);
}
// now place the remaining css sites/default/files back into the template variable for rendering
$vars['styles'] = drupal_get_css($css);
?>

You should now be able to see that the CSS sites/default/files have disappeared from the head of your document – a pretty drastic step, but it’s pretty much guaranteed…!

Feb 07 2008
Nik
Feb 07

Recently released – a Javascript aggregator module for Drupal 5. This function is included in core in Drupal 6, but users of 5.x are left hanging on. Enter, the Javascript Aggregator module.

I put this onto my own site straight away to test it out, as I’m using shared hosting at the moment, and I want to reduce page load times as much as I can. It seems to work just fine. The module requires no core patches, and also includes an interface for file exceptions, as TinyMCE (natch) causes the module to fail.

Theming purists should maybe note the issue I filed here, which clears up a problem with having to add unnecessary PHP into the page.tpl.php file. It looks like this will get sorted and patched fairly soon, though [Edit: that’s fixed]. This is another great way to speed up Drupal page load times!

Posted by Nik - February 7th, 2008 at 11:58am

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