Overriding WooCommerce (or any WordPress plugin) functions: a semi-solution

Here’s a question I’ve spent the past few days dealing with based on an issue a client had. How can you override a function in the WooCommerce core? The simple and dismissive answer is, you can’t. I tried a lot of different possible solutions. Eventually you may just be able to track down a solution that works in your specific case, but there’s no general rule that works universally. In this post I am going to discuss the specific problem that was the impetus for this journey, explore some of the challenges the situation presents, and describe the ugly but marginally tolerable solution that worked in this one narrow case.

What’s the problem?

Why would you ever want to override existing WooCommerce functionality? Well, maybe I need to start with a simpler question. What is WooCommerce? It’s a plugin for WordPress that adds extensive e-commerce capabilities. It’s hugely popular and, for the most part, really good. But it’s not perfect, and it’s not right for every possible scenario.

We have a client with a huge number of products, and a huge number of variants of those products. Sizes, colors, styles, etc. And as it happens, this client doesn’t give all of those variants their own SKU. But they do give some of them a SKU, and sometimes multiple variants share the same SKU.

Don’t get all principled about the definition of a SKU. They’re totally arbitrary and different businesses will use them in different ways. It’s not my job to police my clients’ SKU conventions, just to give them a way to use them within WooCommerce.

So now, here’s the problem. The client had been able to give multiple variants the same SKU. But a couple months back, we ran an available WooCommerce update, and it “broke” this “feature” for them. As it happens it was really fixing this bug in the system that allowed variants with duplicate SKUs. At least that was the argument in favor of the change. Even though I agree with coenjacobs who wrote: “I can think of use cases where this might be expected behaviour. Variations don’t necessarily require a product to be completely different, therefore duplicate SKUs might be required.”

Yes, I can think of use cases like that… my client’s website! Unfortunately there’s no turning back, so we need to find a solution.

The solution, on the surface, is extremely simple. Inside the WooCommerce plugin there’s a deeply buried file called includes/admin/meta-boxes/class-wc-meta-box-product-data.php that has this bit of code at line 1547:

$unique_sku = wc_product_has_unique_sku( $variation_id, $new_sku );

That code happens inside a method that saves variant data. It’s calling a function that checks that the SKU on the variant is unique. This block of code was apparently added in version 2.1.12 of WooCommerce. All you really need to do to “fix” the problem and restore the old functionality that allowed duplicates is to comment out that line of code, and replace it with this:

$unique_sku = true;

(OK, technically it would probably be better to write another function that makes sure that the SKU is unique in the system except within variants of this particular product, but that’s a topic for another day… or at least a passing mention again at the end of this post.)

I put “fix” in quotation marks up above because, trust me, you’re not fixing anything by editing the core files of a plugin directly. You’re just setting yourself up for future problems. Next time there’s an update, and a well-meaning person runs it — say, oh, I don’t know… you — your changes will get wiped out. You need a way to make your changes somewhere else. You need to be able to override this functionality without changing the plugin itself. This is WordPress 101, people. Maybe you need a refresher course. (It’s all ball bearings nowadays!)

What makes this so difficult?

As it happens, there are a number of exacerbating factors that make fixing this particular problem in a safe way incredibly challenging.

1) PHP doesn’t let you override functions that have already been declared. If it did, I could very easily just redefine wc_product_has_unique_sku() in my own way, a way that does mostly what it currently does, but with the added allowance for duplicate SKUs for variants of the same product.

One potential solution I tried pursuing, even though I knew it wouldn’t work, was to “redefine” the function inside an if (function_exists()) conditional. This is a fairly common feature of WordPress and some plugins that is called making functions “pluggable”. The idea is that if you declare your function inside that conditional, and it’s already been declared somewhere else earlier, no problem. Files inside your theme are loaded first, so this allows you to write your own versions of pluggable functions and override the defaults.

But this “pluggable” conditional has to be in the core files in order to work. I can’t put that conditional into my functions.php file, because either it loads after the plugin (it doesn’t, but bear with me for sake of argument) and doesn’t do anything, or (as it actually happens) it loads before the plugin, declares the function, and then when the plugin loads PHP throws a fatal error.

Like I said, I knew this wouldn’t work, for the exact reasons described above. But I tried it anyway. Guess what. It didn’t work. Fatal error. Moving on…

2) WordPress offers “hooks” to allow you to override functionality, but only if that functionality uses a hook. Not every function call in WordPress, or in plugins, uses hooks. A lot of them do, and that’s great. But the call to wc_product_has_unique_sku() at line 1547 in the save_variations() method of the WC_Meta_Box_Product_Data class, found inside that aforementioned class-wc-meta-box-product-data.php file, does not. At least not directly. So the only way this solution will work (hint, hint) is if you can find a place farther up the logic chain that does.

3) The one way PHP does let you override functions is through a PECL extension that is probably not installed on your web server. If I were hosting this client’s site myself, on my own server, I’d just install the necessary PECL extension so the override_function() function was available to me. But I’m not, so I can’t.

4) As designed, WooCommerce only allows you to override files inside its templates subdirectory. You may see a lot out there about solving all of these kinds of problems I’m describing by simply creating a woocommerce directory inside your theme and replicating files from WooCommerce there. Yeah, you can do that, and it works great. As long as the file you’re trying to override is in the templates directory of the main WooCommerce plugin. Anything else is untouchable. Trust me. I tried.

But… you said you found a solution, right?

Yes. I did.

Well… what is it?

You’re not going to like it.

A solution is a solution. C’mon, just tell me!

OK, fine. I found a solution that is ugly and messy, and only works in my specific case. And it’s not at all the kind of ideal “replace this function with my own” solution I wanted.

I did a manual backtrace-of-sorts on the functionality surrounding line 1547 in class-wc-meta-box-product-data.php to see if I could find an action, somewhere, that I could hook into. And I did. But it isn’t pretty.

You see, like I said, line 1547, the one and only line in the entire body of WooCommerce that I want to change — I don’t even want to edit the wc_product_has_unique_sku() function because it’s necessary elsewhere; I just want to not run it right here — is deeply nestled inside a delightfully complex method called save_variations() that weighs in at 263 lines of code. What’s worse, this method is only called in one place in the entirety of WooCommerce, and that’s in the save() method within the same file. Guess what… the save() method is 434 lines of code. Yikes.

Next up, I needed to find any places in the WooCommerce code base where that method appears. And as it happens it only appears in one place… and it’s an add_action() call! At last, we have a hook!

In the file includes/admin/class-wc-admin-meta-boxes.php, at line 50, we have the following:

add_action( ‘woocommerce_process_product_meta’, ‘WC_Meta_Box_Product_Data::save’, 10, 2 );

That’s it. One static instance of the method, inside an action. That’s something we can use, but… oh, no. Does that really mean… but wait, you’re sure there’s… uh… oh.

Yes, we can override this method by creating our own class that extends WC_Meta_Box_Product_Data, and then removing this action and replacing it with another one that runs the save() method inside our class instead. And we can change anything we want inside that save() class. And it will work. I tried it.

But it means we have to replicate all of the rest of that code inside our custom class. We don’t have to replace any of the methods of the parent class that we’re not changing. But we have to replace both the save_variations() method and the save() method that calls it, because at line 1442, there’s… this:

self::save_variations( $post_id, $post );

That self means that if we just change save_variations() in our class, it’ll never run. We need to bring over the entirety of both of those methods into our class — all combined 697 lines of code — just to replace that one line.

That said… it does work. And it’s safe from future WooCommerce plugin updates. As long as nothing else in those two methods gets changed.

Practically speaking, it means remembering before every WooCommerce update to do a diff on class-wc-admin-meta-boxes.php between the new version and the version you’re currently running, then making sure any changes that appear in those two methods get copied over into your child class.

Is this really any better than editing the core and just remembering to redo your change every time there’s an update? I think you could argue that it’s not, but I’ll still say it is, because at least your code won’t disappear when the update gets run. Something still might break… but if this file hasn’t been changed, it won’t. Editing the core file directly guarantees your fix will break when an update runs, and it’s much harder to get your code back. (Hope you kept a backup.)

Let’s put it all together

Now that you’ve seen what we need to do, let’s do it. This goes into your functions.php file in your theme, or, as I have it, into a separate file that gets include()’d into your functions.php file.

<?php

class My_WC_Meta_Box_Product_Data extends WC_Meta_Box_Product_Data {

  public static function save( $post_id, $post ) {
    // Yes, put the ENTIRE WC_Meta_Box_Product_Data::save() method in here, unchanged.
  }

  public static function save_variations( $post_id, $post ) {
    // Again, the ENTIRE WC_Meta_Box_Product_Data::save_variations() method goes in here,
    // unchanged EXCEPT for this line, which you comment out and replace with the next:
    // $unique_sku = wc_product_has_unique_sku( $variation_id, $new_sku );
    $unique_sku = true;
    // Everything else that came after in the original method goes here too.
  }
}

remove_action('woocommerce_process_product_meta', 'WC_Meta_Box_Product_Data::save', 10);
add_action('woocommerce_process_product_meta', 'My_WC_Meta_Box_Product_Data::save', 10);

?>

Well, that’s profoundly unsatisfying

I agree. This is not the solution I was looking for. I’m sure it’s not the solution you’re looking for. But for now at least it seems like the best only way.

What I would really like as a solution requires changes to the WooCommerce core:

1) This would really be great: a checkbox option in the WooCommerce settings to allow duplicate SKUs for variants. I built my own CMS and used it for almost every project for years before fully embracing WordPress as a general purpose CMS. Eventually, it got to a point where some clients were asking for functionality changes that might conflict with ways other clients were using the system. Any time I introduced a significant functionality change, even if it was fixing a “bug” that some clients might assume was a “feature”, I always set it up as a configurable option in the system, with the default setting being the way it already was.

2) Not as great, but this would still help: all WooCommerce functions being made “pluggable”. If only the declaration of the wc_product_has_unique_sku() function inside wc-product-functions.php were wrapped inside an if (function_exists()) conditional, I would be able to write my own version of that function that allowed duplicate variant SKUs. In fact that would be a much more powerful solution than what I’ve done, because I could write it to make sure the variants’ SKUs were only duplicates within the same product. I haven’t tested it thoroughly yet, but I’m pretty sure my actual solution will do nothing to prevent duplicate SKUs for variants of different products, as well as the same product.

Here’s hoping someone with commit rights on the WooCommerce project sees these suggestions and acts on them. In the meantime, I’ll have to live with my convoluted solution. I hope this helps shed some light on ways you might be able to find your own way around this sticky problem.

Responsive images with srcset in WordPress

As a developer, I am somewhat conservative. I believe strongly in the importance of web standards, and I am reluctant to be an early adopter of any new techniques or, even worse, non-standard workarounds for limitations in existing standards. I’d rather live with the limitations until a proper standard — or at least a de facto standard — takes hold.

One of the latest issues to challenge my approach has been responsive images. I’ve settled into a pattern of going with “1.5x” quality images, trying to strike a balance between quality on large high-res displays and a reasonable file size. But it really doesn’t do either very well.

Today’s issue of A List Apart features an exciting article:

Responsive Images in Practice

Yes! In practice! Let’s do this!

There have been a couple of competing proposals for handling responsive image sets, and I am pleased to see the srcset attribute begin to emerge as the winner. The biggest plus it has for me is that it degrades gracefully for older browsers that don’t support it.

Well, before I had even finished reading the article I started thinking about how I could leverage the build-in image sizing mechanism in WordPress to use srcset. I haven’t looked around — I’m sure someone else has already created a plugin that perfectly nails what I am attempting to do here. And, to be honest, I haven’t extensively tested this code yet, although I did drop it into a site I’m currently working on, just to be sure it doesn’t throw a fatal error and that it actually does render the HTML <img> tag as advertised.

function img_with_srcset($attachment_id, $default_size='medium', $echo=true) {
  $output = null;

  // Get image metadata
  $image = wp_get_attachment_metadata($attachment_id);

  // Set upload directory for image URLs
  $upload_dir = wp_upload_dir();
  $upload_url = $upload_dir['baseurl'] . '/' . dirname($image['file']) . '/';

  // Build array of sizes for srcset attribute
  $sizes = array();
  foreach ($image['sizes'] as $size) {
    $sizes[$size['width']] = $upload_url . $size['file'] . ' ' . $size['width'] . 'w';
  }
  
  // Sort sizes, largest first
  krsort($sizes);

  // Get image alt text
  $image_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);

  // Generate output <img> tag
  if (!empty($sizes)) {
    $output =  '<img srcset="' . implode(', ', $sizes) . '" ' .
               'src="' . $upload_url . $image['sizes'][$default_size]['file'] . '" ' .
               'alt="' . esc_html($image_alt) . '" />';
  }
  
  // Return/echo output
  if ($echo) {
    echo $output;
    return true;
  }
  else {
    return $output;
  }
}

Let’s just examine what’s going on here.

The function takes three input parameters. An attachment ID, a default size (for the old school src attribute), and a boolean for whether to echo the output or just return it.

First, we get the attachment metadata and put it into $image. You can see more about what the wp_get_attachment_metadata() function does here.

Next, we set up the $upload_url variable to be the full base URL to the WordPress uploads directory. That’s because the metadata output only includes the filename of each sized image, not its full URL.

Then we loop through all of the sizes in the metadata output, generating a series of strings containing the image URL and its width, for use in the srcset attribute. We put these into an array because we need to manipulate the list a bit: we need to sort it so the largest images come first, and then later we need to implode() this into a comma-separated string.

Of course we also need the image’s alt text, so we grab that with get_post_meta() which you can read more about here.

Finally, assuming we actually have some size data, we build the <img> tag, complete with srcset attribute! Then we either echo or return it, as determined by the $echo input parameter.

Something else I’d like to try with this is taking it a step further by adding a filter that processes page content, looking for <img> tags, and automatically inserts the appropriate srcset attribute.

There you have it. I welcome anyone who’s reading this to give the function a try in your WordPress site, and let me know how it goes!

From the Stupid PHP Tricks files: rounding numbers and creeping inaccuracy

This morning as I walked to the studio I was doing what geeks do best: pondering a slightly esoteric mathematical quandary.

Glass Half Full by S_novaIngraining the American spirit of optimism at a young age, and under dubious circumstances, our schools always taught rounding numbers in a peculiar way. You always round your decimal values to the nearest integer. That part makes sense. But what if the decimal is .5 — exactly half? In my education, at least until late in high school (or was it college?), we were always taught to round up! The glass is half full. Optimism.

Eventually — far later than it should have been, I think — the concept was introduced that always rounding .5 up is not really that accurate, statistically speaking. It might be nice in the case of a single number to be an optimist and think a solid half is good as a whole, but in aggregate this thinking introduces a problem.

If you have a whole lot of numbers, and you’re always rounding your halves up, eventually your totals are going to be grossly inaccurate.

Of course, the same would happen if you were ever the pessimist and always rounded down.

The solution, I later learned, was to round halves up or down, depending upon the integer value that precedes them. Which way you go doesn’t really matter, as long as you’re consistent, but as it happens, I learned it as such: if the integer is odd, round up; if it is even, round down.

In my work, I write a lot of PHP code. Most of it is of the extremely practical variety; I’m building websites for clients, after all. But every once in a while I like to indulge my coding abilities in a bit of frivolous experimentation, and so today I produced a little PHP script that generates 10,000 random numbers between 1 and 100, with one decimal place, and then it shows the actual sum and average of those numbers, along with what you get as the sum and average if you go through all 10,000 numbers and round them to whole integers by the various methods described above. Try it for yourself!

Any time the rounded average is different from the “precise” (and I use that term somewhat loosely) average, it is displayed in red. Interestingly, and not at all surprisingly, when you always round halves in one direction or the other, at least one of those directions will (almost) always yield an incorrect average. Yet if you use the “even or odd” methods, both of those methods will almost always yield a correct average.

It’s all about the aggregate.

Sendmail not working? Maybe your server’s IP is on a block list

This is pretty arcane, even for me, but since I spent several hours troubleshooting this problem this week — and the solution was nowhere to be found on Google — I figured it was worth sharing.

My CMS, cms34, as I’ve mentioned a few times before, is built on CakePHP. Some features of cms34 include automatically generated email messages. CakePHP has a nice email component that facilitates a lot of this work. It can be configured to use an SMTP server, but by default it sends mail directly from the web server using whatever you have installed on the server, either the ubiquitous sendmail or the more powerful (and capitalized) Postfix. Don’t unleash a deluge of flame comments on me, but I’m using sendmail. So be it.

All was working well until a few weeks ago, when suddenly none of the mails were being sent. There were no errors on the website; the messages just wouldn’t go through. What was more confusing was that messages being sent to my own domain did go through, but for those being sent to my clients’ domains, nothing.

Nothing except log entries, that is. Specifically, the mail log was filling up with lines like this:

Sep 13 13:45:56 redacted sm-mta[28158]: o8DIjsx0028156:
to=<redacted@redacted.com>, ctladdr=<redacted@redacted.com>
(33/33), delay=00:00:02, xdelay=00:00:01,
mailer=esmtp, pri=120799, relay=redacted.com.
[123.456.789.000], dsn=5.7.1, stat=Service unavailable

(Note that I’ve removed the real email and IP addresses to protect the innocent, namely myself.)

“Service unavailable,” huh? I researched that error extensively, without finding much. Eventually I was led to believe it may be an issue with my hostname, hosts, hosts.allow and/or hosts.deny files.

A few relevant points: 1) my hosts.allow file only contains one (uncommented) line: sendmail: LOCAL and 2) likewise, the hosts.deny file only contains: ALL: PARANOID. I’ll save you some time right here: the problem I had ended up having nothing whatsoever to do with any of these host-related files. Leave ’em alone.

After following a number of these dead ends, I was inspired to check the mail file on the server for the user Apache runs as, in my case www-data. On Ubuntu Linux (and probably other flavors), these mail files can be found in /var/mail. Indeed, there were some interesting things to be found there, namely, a number of references to this URL:

http://www.spamhaus.org/query/bl?ip=123.456.789.000

(Again, the IP address has been changed… and yes, I know that’s not a valid IP address. That’s the point.)

I was not previously aware of The Spamhaus Project, but perhaps I should have been. The reason my messages weren’t getting through was because my server’s IP address was on the PBL: Policy Block List. Essentially, that is a list of all of the IP addresses (or IP ranges) in the world that, according to a well-defined set of rules, have no business acting like SMTP (Simple Mail Transfer Protocol*) servers — the servers that send mail out.

It stands to reason that my server was on this list; technically it’s not an SMTP server. But it’s perfectly legitimate for a web server to be running sendmail or Postfix or something of that nature, and sending messages out from the web applications it runs. Fortunately, it’s easy to get legitimate servers removed from the PBL. Simply fill out a form, verify your identity (via a code sent to you in an email message), and within about an hour, the changes will propagate worldwide.

Success! So if you’re in the same kind of situation I was in, where everything seems to be configured properly but your messages just aren’t going out for some reason, try checking Spamhaus to see if your IP is on the PBL.

* If you made it this far in the post, I shouldn’t have to explain the acronym. But I will anyway, as is my wont.

The new site design, phase two

Lots to choose from...As I mentioned the other day, the initial launch of this new site design was just phase one — window dressing. Window dressing I happen to like a lot, but still… same old clunky underlying structure. But not anymore.

WordPress has a pretty rudimentary home page structure by default. Everything’s just *SPLAT* right out there on the home page. Sure, you can use the <!--more--> tag to trigger some automagic stuff with “Read more” links, but overall, it’s not too fancy. Which isn’t to say it’s bad. Now, the default page layouts for some other open source CMSes like Drupal or Joomla, on the other hand… yeah, they’re bad. (Or at least they were, the last time I bothered to care.)

So while WordPress out of the box doesn’t do much fancy stuff with the home page layout, it’s extensible enough that a crafty developer (or a well-read tinkerer) can do some pretty nifty stuff with it. And that was my goal with this new redesign: I’ve got hundreds of posts dating back over seven years now, and most are eternally buried in the archives. I’m hopeful this new approach will bring some of that older content to light, with the random articles list on the home page and the related articles list at the end of each article page.

I didn’t do it all alone… I had some help from a nifty article from Smashing Magazine: 10 Exceptional WordPress Hacks. In particular I made use of numbers 5 through 8 on this list… with some modification. Some of my changes were purely to suit my taste, but others were to improve the usability of the features and in at least one case to fix a bug in the provided sample code. It’s probably worth discussing the details here.

5. Display Related Posts Without A Plug-In

I set this up in single.php so it will appear at the end of each of my articles. I experimented a bit with matching both tags and categories, but I found (for reasons I didn’t dig deep enough to explain) that WP_Query() does well with either tags or categories, but not both.

And, most importantly, I found (or actually, SLP did) that this sample as given breaks comments on the page. Some commenters on the original post mentioned this problem too, along with its solution: you need to call wp_reset_query(); at the end, to tell WordPress to go back to working with the original post’s content.

I also modified the example to look at all of the tags associated with the post, not just the first (can’t really figure out why the original version did that), and tweaked the HTML/CSS output to suit my design. Here’s a simplified version of the code I’m running:

<?php
// Get related posts
$tags = wp_get_post_tags($post->ID);
if (!empty($tags)) {
  $tag_list = array();
  foreach ((array)$tags as $tag) {
    $tag_list[] = $tag->term_id;
  }
  $args = array(
    'tag__in' => (array)$tag_list,
    'post__not_in' => (array)$post->ID,
    'showposts' => 5,
    'caller_get_posts' => 1,
    'orderby' => 'date'
  );
  $related_posts = new WP_Query($args);
  if ($related_posts->have_posts()) {
    ?>
    <h2>Related Posts</h2>
    <ul>
      <?php
      while ($related_posts->have_posts()) {
        $related_posts->the_post();
        ?>
        <li>
          <a href="<?php the_permalink() ?>"
          rel="bookmark" title="<?php the_title_attribute(); ?>"><?php the_title(); ?></a>
        </li>
        <?php
      }
      ?>
    </ul>
    <?php
  }
  wp_reset_query();
}
?>

My version also uses a special truncation function I wrote to display a short excerpt of each post, not shown here. (And yes, eventually I am going to get syntax highlighting set up.)

6. Automatically Retrieve The First Image From Posts On Your Home Page

This one worked pretty well. I changed the function name to something a little less quirky, and I also added some code to verify that the image actually exists, instead of just looking for an <img src> and assuming everything’s OK. This involved inserting the following block of code into the function immediately before if (empty($first_img)) {:

// Check that file exists
if (!empty($first_img)) {
  // remove http/ https/ ftp
  $src = preg_replace("/^((ht|f)tp(s|):\/\/)/i", "", $first_img);
  // remove domain name from the source url
  $host = $_SERVER["HTTP_HOST"];
  $src = str_replace($host, "", $src);
  $host = str_replace("www.", "", $host);
  $src = str_replace($host, "", $src);
  if (!file_exists(ABSPATH . $src)) {
    unset($first_img);
  }
}

If some of that code looks familiar, that’s because I copied it straight out of the next item. If I were writing it myself, or bothering to rewrite it, I would swap out that slightly cumbersome-looking three lines of str_replace() with a single preg_replace() — although perhaps the original coder knows something I don’t, like that doing it this way is actually faster. It very well could be; I know regular expressions are significantly slower than straight-up string replacements.

7. Resize Images On The Fly

I’m using this in conjunction with all of the other items here, no big surprise. I left it mostly as-is, although I did make one small change. I know well the dangers of scaling images up — they look like crap, basically. But a little scaling up doesn’t hurt. At least, it’s much less glaring than having a big set of uniform-looking images marred by one image that’s slightly smaller than the rest. That happened to me as I was putting this together, so I tweaked the script to allow images to be enlarged up to 1.5 times their original size. In timthumb.php I changed lines 103-109 to be:

// don't allow new width or height to be more than 50% greater than the original
if( $new_width > $width * 1.5 ) {
  $new_width = $width * 1.5;
}
if( $new_height > $height * 1.5 ) {
  $new_height = $height * 1.5;
}

8. Get Your Most Popular Posts Without A Plug-In

There was something about the way this one was written that really bothered me. Maybe it’s just that I have a knee-jerk reaction to seeing SQL code appear directly in a page template. But mostly it was that this didn’t match #5 closely enough. Luckily I discovered along the way that regardless of whether you retrieved post data using WP_Query (which returns an object) or with the $wpdb->get_results() method (which returns an array), you can use the same post functions once you’ve called setup_postdata(). So I kept everything from this example up through that, and then I used my modified version of the output code from #5 and it worked like a charm.

One other thing I’d recommend changing: it’s kind of silly to have the if ($commentcount != 0) conditional in there. Much better to just put WHERE comment_count > 0 in the SQL. I also added a date range to the SQL, to keep the list dynamic. In my case, it’s only looking at the most popular posts over a 3-month range. More active blogs could shorten that time frame. My full query looks like this:

$popular = $wpdb->get_results("SELECT * FROM " . $wpdb->posts .
" WHERE post_date > '" . date('Y-m-d',mktime(0,0,0,date('n')-3,date('j'),date('Y'))) .
"' AND comment_count > 0 ORDER BY comment_count DESC LIMIT 4");

There may have been some other changes I made that were relevant here but I think I covered all of the major ones. The tips on the Smashing Magazine site were invaluable in kick-starting my overhaul of the home page. It was uncanny that I stumbled upon this page today, just as I was getting down to this task anyway. It saved me a ton of time. But it still kept me on my toes, since all of the so-called “hacks” required some additional hacks of my own to get them working, or at least, to get them working the way I wanted!