Remove Node Lists from Taxonomy Pages in Drupal 7

If you use Drupal, you can’t help but love the Taxonomy module. After all, categorizing content goes hand in hand with creating it or managing it, and Taxonomy gives us a nice, flexible framework, especially when you throw fields into the mix.

Unfortunately, every taxonomy term page comes with a default list of all the content classified with that term. Sometimes you might want this, but sometimes you might not. I’ve run into a few situations in which I needed tighter control over the term page display, like that provided by Views, without wanting to override the entire page.

Here’s the snippet of code I created to deal with those situations. Just put the following in the template.php file of your theme folder:

function THEMENAME_preprocess_page(&$vars){   if (arg(0) == 'taxonomy' && arg(1) == 'term' && is_numeric(arg(2))){     unset($vars['page']['content']['system_main']['nodes']);     unset($vars['page']['content']['system_main']['pager']);     unset($vars['page']['content']['system_main']['no_content']);   } } This will remove the default content list, as well as the pager and "No content has been classified with this term" text if either is present. On a default installation, that sets the page back to a blank slate, like an ordinary content page, so you can play around with it from there to your heart's content.

jQuery Default Form Text Function

Often times, in place of labels, designers will choose to put default text in a form field to denote the information that it should contain. This is good design and usability, but it also creates some functional problems. Obviously, the text should disappear when the field is in focus, but what happens if the user clicks away without entering anything, or submits the form before filling in the field?

This handy jQuery function solves those problems. Default text will appear in the field whenever it is empty and not in focus. The user can click it to remove the text, then click away to reveal it again. If the field has been filled out, nothing happens. As a bonus, if the form in question is submitted with default values, those are cleared before the submission takes place to allow for proper validation.

function default_text(selector, text){
  element = $(selector);
  if (element.val() == ''){
    if ($(this).val() == text){
    if ($(this).val() == ''){
    if (element.val() == text){

  default_text('#your_form_element_id', 'Your default text');

How to Enable Ubercart Product Image Zoom with Gallery Thumbnails

In my experience, ecommerce websites are their own beasts, with all sorts of specialized functionality that doesn’t get requested on other websites. One of the most frequent requests, and often times one of the most difficult to fulfill, is enabling product image zoom functionality along with gallery thumbnails that can seamlessly swap out the main image. Here is one way to accomplish that on a Drupal 6 website with Ubercart.

  1. Install the necessary modules. For this solution, you’re going to need CCK, ImageField, ImageCache, and Cloud Zoom (I used to prefer jQZoom, but found it to have less compatibility in WebKit browsers).
  2. Set up your image field. The code provided below is designed to work with the default image handling field creating by Ubercart, but it can work just as well with any CCK image field. Just be sure to swap out the $node->field_image_cache variable with the name of your own field if you’re using something different.
  3. Set up your ImageCache presets. You’re going to need three. Again, the solution below is tailored for use with product images in Ubercart, so I’ve named them accordingly. “product_full” is the main image displayed to the user, “product_zoom” is the larger image shown in the zoom window, and “product_thumbnail” is the gallery thumbnail that allows the user to swap out the main image. You can name these whatever you like; just be sure to change their names in the code as well.
  4. Stop the field from displaying. By default, Cloud Zoom can automatically display the image and zoom window without any need to mess with template files. This is very handy, but it doesn’t support gallery thumbnails, so we’ll need to disable the default behavior. Go into Administer > Content Management > Content Types > Manage fields > Display fields and change the Label, Teaser, and Full node drop-downs for your image field to . Be sure not to click the Exclude boxes, or else the template modifications below will not work properly.
  5. Add this code to your theme file. In the case of Ubercart, this goes in node-product.tpl.php where you’d like the images to appear. If you’re using a different content type, use that in place of product. And again, be sure to switch out the image field variable name or the ImageCache preset names if you’re using something different.

    &lt;?php<br /><br />
    drupal_add_js(drupal_get_path('module', 'cloud_zoom') . '/cloud-zoom/cloud-zoom.1.0.2.min.js');<br />
    drupal_add_css(drupal_get_path('module', 'cloud_zoom') . '/cloud-zoom/cloud-zoom.css');<br />
    if (is_array($node-&gt;field_image_cache) && count($node-&gt;field_image_cache) &gt; 0 && strlen($node-&gt;field_image_cache[0]) &gt; 0){<br /><br />
      // Display the primary image.<br />
      echo l(theme('imagecache', 'product_full', $node-&gt;field_image_cache[0]['filepath'], $node-&gt;field_image_cache[0]['data']['alt'], $node-&gt;field_image_cache[0]['data']['title']), imagecache_create_path('product_zoom', $node-&gt;field_image_cache[0]['filepath']), array('attributes' =&gt; array('class' =&gt; 'cloud-zoom', 'id' =&gt; 'zoom1'), 'html' =&gt; TRUE));<br /><br />
      // Display the gallery thumbnails.<br />
      $num_images = count($node-&gt;field_image_cache);<br />
      if ($num_images &gt; 1){<br />
        for ($i = 0; $i &lt; $num_images; $i++){<br />
          echo '&lt;a class="cloud-zoom-gallery" href="' . base_path() . imagecache_create_path('product_zoom', $node-&gt;field_image_cache[$i]['filepath']) . '" rel="useZoom:\'zoom1\', smallImage:\'' . base_path() . imagecache_create_path('product_full', base_path() . $node-&gt;field_image_cache[$i]['filepath']) . '\'"&gt;' . theme('imagecache', 'product_thumbnail', $node-&gt;field_image_cache[$i]['filepath']) . '&lt;/a&gt;';<br />
        }<br />
      }<br />
    }<br /><br />
  6. Stylize to taste. With a bit of added coding and some CSS, you can make the final product display however you like. For example, I like to put the gallery thumbnails into an unordered list and float them beneath the main image for ease of usability. Your needs may vary.

How to Add Variations to a Drupal Theme

Recently, I did work for a few clients who needed several very similar websites launched in a single project, each of which using an almost identical (yet subtly different) theme. As I started configuring them on Drupal multi-site installations, it got me thinking: Is there a way to take advantage of the same sort of code reuse within a theme?

There are already options for this, of course, such as sub-themes or the Color module. In my case, however, I decided to try something a little different: I used a custom theme setting to add a CSS class to the body tag, then created the theme variations with pure CSS. Here’s how I did it.

Step One: Set Up the Advanced Theme Setting

In case you’re a Drupal themer who doesn’t know this trick, it’s a life-saver. You can configure your theme with a form to collect custom settings, then use those settings in the theme itself. I like to use this for things like phone numbers that don’t deserve their own block region but need to be configurable by the client nonetheless.

There’s a great Drupal article on advanced theme settings, which I won’t bother repeating. As far as theme variants go, all you have to do is include the following code in the theme_settings.php file of your theme folder:


function themename_settings($saved_settings){
$defaults = array(
'variant' =&gt; 'default'
$settings = array_merge($defaults, $saved_settings);

$form['variant'] = array(
'#title' =&gt; t('Variant'),
'#type' =&gt; 'select',
'#default_value' =&gt; array($settings['variant']),
'#options' =&gt; array(
'default' =&gt; 'Default',
'variant_1' =&gt; 'Variant #1',
'variant_2' =&gt; 'Variant #2',
'variant_3' =&gt; 'Variant #3'

 return $form;

This will create a drop-down selection menu on your theme configuration page that allows you to select the desired variant. Be sure to change the keys and values in the #options array to include the CSS class and variant names you want.

Step Two: Hook the Variant Setting into the Template Files

Now that the variant can be defined, it’s time to dynamically include it in your template files. This is accomplished with the theme_get_setting() function. Include the following code at the top of your page.tpl.php file (and any other relevant template files):

$variant = theme_get_setting('variant');
if ($variant == 'default'){

Then, on the body tag in each template, include code to insert the variant as a CSS class:

&lt;body&lt;?php echo ($variant) ? ' class=" . $variant . "' : ''; ?&gt;&gt;

If you want, you can do other useful things with the $variant variable. For example, I took it a step farther and created image subfolders at theme_folder/images/$variant. That way, if I had images that needed to vary, all I had to do was name the images the same and include $variant in the image src attribute.

Step Three: Add CSS to Customize Each Variant

Once the body is being classed according to the theme variant setting, you can do whatever you like to customize each variant. Simply add CSS to your style.css file in order to tweak the theme’s appearance according to the new body class. For example, you might adjust the font face and color of each variant:

#content { color:#000; font-family:Arial; }
.variant_1 #content { color:#F00; font-family:Helvetica; }
.variant_2 #content { color:#00F; font-family:Verdana; }

Possible Uses

The main use of this technique is to provide a fast, easy way to create minor variations in a single Drupal theme. You might add some seasonal stylization for a holiday variant, for example. Or, as in my case, you might have a few small differences between sites using the same theme and want to keep a common code base for ease of maintenance.

Anything more than that and you’re probably better off using one of the aforementioned techniques, such as sub-theming or the Color module. It’s really just a matter of how different your variations are going to be.

Code HTML Email Templates by Breaking the Rules

If you’re anything like me, the first time someone told you to make an email template, you thought, “Piece of cake!” After all, emails use HTML (or the types that needs templates do, at any rate), and HTML is a cinch, right?

As it turns out, you’d be wrong. HTML is a cinch, but the truth is, the more you know about proper HTML coding standards, the harder it is to make a functional email template. That’s because the only way to write them is to break all of those pretty rules you spent so long mastering. Here’s the breakdown:

  • Only use inline styles, if you even use them at all. External or embedded stylesheets will almost certainly be stripped out. Some email clients allow you to declare your styles in the document, but support for this is spotty at best. The only way to guarantee that your styles get parsed is to place them into style attributes on each and every element. Even then, be aware that many useful style attributes (background and position come to mind) are not widely supported.
  • Use tables for layout. As I mentioned above, email clients don’t generally support CSS positioning. The best way to ensure consistent display across clients is to use tables.
  • Exclude the HTML, head, and body tags. A lot of email clients strip them out anyway.
  • Exclude forms and JavaScript. Email clients will ruthlessly omit your dynamic functionality, and your email’s spam profile will be much higher for the attempt.
  • Don’t worry about SEO. Emails don’t get indexed, anyway, so the code can (and will be) as cluttered as you like. Make sure it can still be parsed by screen readers, of course; we wouldn’t want to turn away disabled subscribers. Just don’t concern yourself with making an email rank.

Why are the rules set this way? I blame the proliferation of spam and the lack of standards between email clients. HTML is great, but it gives spammers too much control, and the knee-jerk response is to strip out everything until you’re left with… well, this. Don’t believe me? Try testing your email template on the dozens (probably hundreds) of email clients in use. And you thought cross-browser compatibility testing was bad…

So, blasphemy though it may be to us coding purists, these are the standards you have to keep in mind when coding email templates. As a rule of thumb, just break every rule in the book and you’ll be on the right track. And if that makes you feel dirty, you can always read a good book on HTML 5 for absolution. 😉

Attach Files to Taxonomy Terms in Drupal

From what I can tell, there are plenty of ways to attach files to nodes in Drupal, and even a few ways to attach them to users, but none that I could find to attach them to taxonomy terms (images through the Taxonomy Image module, for sure, but not files in general). My guess is that there just aren’t enough people who need to do so.

In a recent project, however, I found myself needing just that. The client in question was using a vocabulary of fonts that had to be associated with their respective font files for JavaScript. After beating my head against the problem for awhile, I managed to figure out a solution. Instead of attaching a file directly to the taxonomy term, I assigned the same vocabulary to a new content type to hold the attachment. Here’s how it works:

  1. Go to Administer > Site building > Modules and make sure the core Taxonomy and Upload modules are enabled.
  2. Go to Administer > Content management > Content types and click the link at the bottom to Add a new content type.
  3. Give your new content type an intuitive name (e.g., Font File) and enable attachments under Workflow settings. Configure the other settings as you see fit and hit Save.
  4. Go to Administer > Content management > Taxonomy and edit the vocabulary for which the file attachments are intended (e.g., Fonts). Check the box next to your new content type and click Save.

Great! Now you have a way of associating a term with a file, albeit indirectly through a node. How you use that information will, of course, vary. In my case, the font files needed to be populated into a drop-down list of font choices for use by JavaScript. Here’s how you might go about accessing the attachment from the database:


// Fetch the file path of the most recently uploaded file attached to a node that shares the given term
$attachment_path = db_result(db_query("

SELECT filepath FROM {files} f JOIN {upload} u ON f.fid = u.fid JOIN {node} n ON u.vid = n.vid JOIN {term_node} tn ON n.vid = tn.vid WHERE n.type = '%s' AND tn.tid = %d ORDER BY f.timestamp DESC LIMIT 1



Of course, the best solution would be to create a custom module to handle all of this. In my case, there simply wasn’t enough time for that. I invite some enterprising developer to invalidate my technique by creating a module that allows file attachments on taxonomy terms. Until then, I hope someone finds my roundabout hack useful.

7 Signals of Comment Spam

Recently, I’ve found myself giving the same advice to several of my clients, many of whom are still getting into the swing of blogging. All too often, they’re uncertain about whether or not to mark a comment as spam. Caught between the desire to avoid spam but foster legitimate conversation, they come to me, and these are the seven signals I tell them to look out for:

  1. Links to Spammy Sites. The biggest red flag should always be links provided by the commenter. Visit them and you can usually figure out pretty quickly if the commenter is working an angle. I’ve marked well-thought-out, on-topic comments as spam before because they linked to sites I didn’t care to be associated with.
  2. Lots of Links. There are very few situations I’ve ever seen in which a comment with more than two or three links isn’t spam. If the comment is chock full of them, or, worse, is nothing but a list of links, you should hear alarm bells ringing.
  3. Overuse of Keywords. Perhaps one of the most obvious cues is when a comment seems like little more than a list of keywords. This may be blatant, or the keywords may be hidden within the comment, at the end of the comment, or in the commenter’s name. This sort of spam is easy to catch, but you have to be willing to give your comments more than a cursory glance.
  4. Off-topic. While not a clear signal in and of itself, the topic of a comment can nevertheless be an important clue. Ask yourself what the commenter is talking about. Does it flow from the article in a natural way, or is it marginally related at best? Does it unambiguously mention anything from the article? The more it strays from the topic at hand, the more likely it’s copied-and-pasted junk.
  5. Complimentary. Make no mistake; spammers will play to your ego if they think it will get their comments posted. Beware comments that pay too much respect to your work. They may just be buttering you up to compromise your better judgment.
  6. Irregular Size. Comments vary in length, but extremely short or long comments beg greater scrutiny.
  7. Poor Grammar. This isn’t to say that ordinary commenters don’t have atrocious grammar some of the time. However, it’s been my experience that most spammers have terrible grammar, even to the point of being nonsensical. Whether this is because English isn’t their native language, the comment is computer-generated, or some other reason, I couldn’t say. Whatever the case, it’s something to keep an eye out for.

Remember; a comment may have one or more of these features and still be perfectly legitimate. When in doubt, ask yourself this: What value does this comment provide to my readers? If you find yourself on the low or negative end of the spectrum, toss the comment without a second thought.

Selectable Items per Page Hack for Drupal 6 Views

I’m sure regular contributors to Drupal will cringe at what I’m about to show you, but I found it to be a handy trick. Let’s say you’re using the Views module in Drupal 6 and you want a way to dynamically control the number of items listed in a view. In my case, a client I was working for wanted to be able to select the number of products displayed per page.

Reasonable request, right? Unfortunately, Views doesn’t handle it out of the box. After a bit of fiddling, I managed to come up with the following hack. Mind you, I use the term “hack” here literally; you should never edit a module like this if you can avoid it, if only because it makes updates painful later on.

The operative code is in the pre_execute() function on line 1795 of modules/plugins/ It normally looks like this:


As the name suggests, anything you feed to this function will set the number of items displayed by the view. In my case, I wanted to use a URL variable coupled with a drop-down menu and some jQuery to toggle it, so I changed it like so:

$this-&gt;view-&gt;set_items_per_page((($_GET['items-per-page'] &gt; 0) ? $_GET['items-per-page'] : $this-&gt;get_option('items_per_page')));

Voila! One line of code and you’re off to the races. Type ‘?items-per-page=X’ into the URL (with X being a number, of course) and it should work like a charm. The pagination even follows suit.

For those who want the full solution, here’s the code for the drop-down menu. You’ll need to add it to a view-specific template file. Just follow the pattern to create your own items per page options.

&lt;?php $items_per_page_override = ($_GET['items-per-page'] &gt; 0) ? $_GET['items-per-page'] : 12; ?&gt;
&lt;div id="items-per-page-selector"&gt;&lt;strong&gt;Items per Page:&lt;/strong&gt; &lt;select&gt;
&lt;option value="12"&lt;?php echo ($items_per_page_override == 12) ? ' selected="selected"' : ''; ?&gt;&gt;12&lt;/option&gt;
&lt;option value="18"&lt;?php echo ($items_per_page_override == 18) ? ' selected="selected"' : ''; ?&gt;&gt;18&lt;/option&gt;
&lt;option value="24"&lt;?php echo ($items_per_page_override == 24) ? ' selected="selected"' : ''; ?&gt;&gt;24&lt;/option&gt;
&lt;option value="9999"&lt;?php echo ($items_per_page_override == 9999) ? ' selected="selected"' : ''; ?&gt;&gt;All&lt;/option&gt;

And here is the jQuery that controls the behavior of the drop-down menu.

$('#items-per-page-selector select').change(function(){
window.location = '?items-per-page=' + $(this).val();

How to Manage External Pages within WordPress

Let’s say you install WordPress in a subdirectory of your site, but you want to use it to manage specific pages in the root directory, as well. The ideal solution would be to move the entire WordPress installation into the root directory, but that may not always be feasible. As it turns out, you can use the following .htaccess code to accomplish the task without moving WordPress or changing any URLs:

&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} <em>existing-filename.php</em>
ReWriteRule . /<em>wordpress-directory</em>/<em>wordpress-nicename</em>/ [P,L]

The code above should go into the .htaccess file in your base directory, not in your WordPress directory. Change existing-filename.php to the relative file path of the file you’d like to manage from within WordPress, change wordpress-directory to the name of the directory where WordPress is installed, and change wordpress-nicename to the URL path of the page within WordPress that you’d like to substitute for the original.

Voila! Whatever you’ve got on the page within WordPress should now show up when users go to the original URL. Mesh your WordPress template with the rest of your site design and they won’t even realize the bait and switch.

Elsewhere: Google Analytics Visitor Counts by Hour of Day and Day of Week

I just posted Google Analytics Visitor Counts by Hour of Day and Day of Week on

Good news and bad news. I’m a pessimist realist, so we’ll start with the bad news. Bad news: Your web team says the site has to go down for maintenance. For several hours in the near future, your company’s presence on the web will be invisible to any potential customer. Good news: You get to […]

Read the whole article on