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 mitigating 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 a conditional that uses 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 uses “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() class and the save() class 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.

Micro-Prog

Last night I learned about the Free Music Archive’s microSong challenge — create a song 15 seconds or shorter — and I knew immediately that I wanted to participate.

But not just participate. I wanted to record a prog rock epic suite that was 15 seconds long. Today I took the challenge. My song has been submitted, but as of this writing it’s not yet up on the site. However, that doesn’t stop me from sharing it here, because the contest requires that each piece of music be released under Creative Commons Zero license. So, here it is! Do whatever you want with it! No one can stop you!

The piece of music I created is called The Fall of the Village of the Infinitesimal Forest at the Hands of the Royal Aggressor [An Epic in Seven Parts]. In fact it’s not just seven parts, it’s seven measures.

In those seven measures there are 6 time signature changes (6/8, 7/8, two measures of 2/4, 7/8, 9/8 and 5/16). The song features a recorder trio (yes, those are real recorders, feebly played by me); MIDI drums, electric piano, B3 organ, Mellotron and analog synth; and real electric bass, two electric guitars and an acoustic guitar.

Here are the titles of the seven parts (and their time signatures):

I. In Which the Villagers Engage in Their Melancholy Dance [6/8]
II. Theme of the Royal Aggressor [7/8]
III. The Battle of the Infinitesimal Forest [2/4]
IV. A Hero Awakens in the Gloaming [2/4]
V. In Which the Hero Mourns His Fallen Love [7/8]
VI. Theme of the Royal Aggressor [Reprise] [9/8]
VII. In Which the Lone Survivor Surveys the Ruins [5/16]

And… here’s the music:

Installing Ubuntu on an HP Stream Mini PC

c04526282When I heard the news a few weeks ago that HP was introducing a cute little blue mini PC that would only cost $180, my first thought was, “I want one!” And when I heard that the Stream Mini would ship with Windows 8.1 preinstalled, my second thought was, “And when I get it the first thing I’m going to do is install Ubuntu on it.”

Easier said than done.

The actual process of installing Ubuntu is not difficult. The difficulty proved to be in simply getting the Stream Mini to boot from a USB stick.

tl;dr

The HP Stream Mini has UEFI boot. If you hit F12 at boot, you get the UEFI boot selector screen, but that’s no good because it won’t show the USB stick. For HP devices like this you need to hit F10 on boot to access the UEFI/BIOS configuration tools. From this point I trust you can find the settings to change the boot order. Just put your USB stick ahead of the hard drive in the order and you’re set.

Actually, I do prefer to read the whole thing, no matter how long it is

Now, for those of you who want the full story, here’s how it goes. I’ll say up front that I primarily use a Mac, so my instructions for preparing the USB stick are Mac-only.

Actually, not quite true. Most of what I’m describing is done at the UNIX command line, so for a UNIX/Linux system, the experience should be similar to this, but you don’t have to convert the .iso file. Also, for the first couple of steps I’m using the Mac-only command diskutil. The instructions on the Raspberry Pi page mentioned in step 2 use the generic UNIX equivalents.

For Windows… you’re on your own. It involves crappy third-party apps you have to download and, well, yuck. I started out all of this trying to get it set up using the Windows 8.1 install that came on the HP Stream Mini, but I quickly gave up and switched to my Mac. (The official Ubuntu docs have instructions for Windows that I read before giving up.)

Step 1: Download Ubuntu

There are many options (including other Linux distros), but if you’re undecided, you probably want to get yourself a copy of the latest version (or latest LTS [Long Term Support] version) of Ubuntu Desktop. Be sure to go for the 64-bit version. At the time of this writing, Ubuntu 14.04.1 LTS is the latest and greatest.

Step 2: Prepare your USB stick

Déjà vu? No, I really have written about this before. More or less. While there are some minor differences, this is basically the same process I described for preparing an SD card with Raspbian for a Raspberry Pi using a Mac.

You can read the even lengthier details there, but basically, you want to:

1) Insert a USB stick (I recommend at least 2 GB, but who can even find one under 4 GB these days?) into a USB port on the Mac. Make sure you don’t care about losing all of the data on this USB stick, because you will.

2) Open Terminal and save yourself some extra typing in the immediate future by starting with: sudo -s

3) Type diskutil list and press Enter to see a list of volumes. Look for your USB stick and note what it says at the right under IDENTIFIER. There’s a good chance it’s going to be disk1s1 but it could be something else. You just need to know the disk1 part. Don’t worry about the s1. Remember this for later.

4) Unmount the disk. Enter this command: diskutil unmountDisk /dev/disk1 being sure to replace the 1 with whatever number you saw in the previous step.

5) Find your Ubuntu installer image. It’s probably in your Downloads folder. Type cd ~/Downloads and press Enter. Then type ls -al and look for the Ubuntu filename, which likely ends with .iso. You’re going to need to convert this into a .img file.

6) Run this command: hdiutil convert input.iso -format UDRW -o output.img but be sure to replace input.iso with the actual filename of your Ubuntu .iso file. You can also change output.img to whatever you want… or at least the output part. This is the filename for the new .img file.

7) Run ls -al again. Mac OS X probably “helpfully” stuck a .dmg extension on your .img file, so run this: mv output.img.dmg output.img replacing both instances of output as appropriate.

OK, now we are actually ready to write Ubuntu onto the USB stick. Run this command:

dd bs=1m if=output.img of=/dev/rdisk1

Be sure to use the correct name for your .img file, and the correct disk number in that rdisk1 bit. Note that the r I added stands for “raw” and it just makes the process go a bit faster.

Now, wait. It shouldn’t take too long, but it might be a minute or so. Once you get the # command prompt again, your USB stick is ready to go. You’ll probably also get a system dialogue box warning you that the disk you inserted is unreadable. Just ignore that. Remove the USB stick from your Mac and insert it into the (powered down) HP Stream Mini.

Step 3: Boot the HP Stream Mini and access the UEFI/BIOS configuration

With the USB stick in one of the HP Stream Mini’s USB ports, power on, then immediately press and hold (or, if you like, frantically tap repeatedly) the F10 key on your keyboard. This should bring up the UEFI/BIOS configuration tool.

hp01

First, go to the Storage menu and choose Boot Order. The dialogue below will appear.

hp02

I recommend disabling UEFI Boot Sources as shown here. Then under Legacy Boot Sources look for your USB stick (not shown here because I didn’t have one inserted when I took the photo). Follow the instructions in the lower right of the box to select and change the order of the boot devices. Make sure your USB stick is above Hard Drive in the list. Press F10 to confirm.

hp03

Now go to the File menu and choose Save Changes and Exit. The computer will now proceed to boot up from the USB stick.

Step 4: Run the Ubuntu installer

This is pretty straightforward. Unlike earlier Linux distributions, Ubuntu these days has a very polished installer program that should feel very familiar to anyone who’s ever installed Windows or Mac OS X on a computer before. There are several steps such as picking your language, location and keyboard layout, but just follow the on-screen instructions and wait for the process to complete, which in my experience took about 20 minutes.

After installation is complete, the system will ask you to reboot, then remove the USB stick and press Enter. Follow those instructions and in less than a minute you should be up and running with a clean install of Ubuntu!

Top 5 Albums of 2014

This year’s list requires an asterisk. Or two. I have not yet listened to the Gone Girl soundtrack by Trent Reznor and Atticus Ross. If past experience with their soundtrack work is any indication, I’m likely to consider it the best album of 2014.

I’m not sure why I haven’t listened to it yet. I haven’t seen the movie (or read the book). But that didn’t stop me in the past. I only managed to sit through half of The Social Network and I never saw The Girl with the Dragon Tattoo either. And yet, I bought and thoroughly enjoyed both of those soundtracks.

Beyond that omission, the list this year also suffers from my general lack of enthusiasm for the music that came out this year. I bought far fewer albums than I have in most years over the past 15 years (ever since Kid A awakened me from the disinterest in what was happening in contemporary music that plagued me throughout the ’90s).

Nevertheless, here’s the list.

5. “Weird Al” Yankovic — Mandatory Fun
I was a huge “Weird Al” fan as a kid. I owned several of his parodies on 45, and had the full Dare to Be Stupid album on vinyl as well. But as an adult, I stopped paying attention except when songs like “All About the Pentiums” or “White and Nerdy” would blip into the collective geek consciousness. Al’s genius with this album was to release a video a day for 8 days around the release of the album. It really got my attention, and got his songs into my brain enough that I had to buy it. Songs like “Foil” and “Word Crimes” are pure “Weird Al” genius, but don’t write off his clever style parodies like the epic “Jackson Park Express.” I still feel a little weird putting Al on the list, but this album really was one of the best I heard this year.

4. Foo Fighters — Sonic Highways
This is a solid Foo Fighters album. I wasn’t really into them for their first 3 albums or so, but from In Your Honor onward, they’ve been my favorite rock band doing new music. In fact I sometimes think they’re the only commercially successful band still producing new, good music I would unambiguously assign to that genre. The band made a huge deal out of Sonic Highways, but to be honest… I think it’s probably their weakest album since I really got into listening to them. I do like it, but I’ll take Wasting Light over this any day.

3. Aphex Twin — Syro
I really dig the music Richard D. James produces. But I have to confess I haven’t gotten into much of it because I find most of his cover art so off-putting. Superficial and silly, but there it is. Syro features a simple green cover with his logo symbol though, so it doesn’t bother me… probably because I have never figured out what it’s supposed to represent.

2. Tycho — Awake
I’ve been a big Tycho fan since I first heard their previous album. I regret missing the chance to see them at First Avenue this year. I actually had tickets to the show but I wasn’t feeling well that night and blew it off. A big regret, to be sure, but it’s tempered by having this great album to listen to. Definitely my favorite of the year. At least, other than…

1. Room 34 — 5mi
Yeah, that’s me. It’s unbelievably conceited to rank my own album at the top of the list, but to be honest, this is just about the only album that I’ve really found compelling all year, and it’s the one I’m most proud of to date. I’ve probably listened to it hundreds of times by now.

The inspiration came in early July, when I was running in a 5-mile race. I had never run any significant distance without listening to music, but somehow I had arrived at the race that day without my earbuds, forcing me to listen to nothing but the sound of my own breathing and footsteps (and, to a lesser extent, those of the runners around me, but it was a small race, so I was alone for most of it).

The rhythmic counterpoint of my steps and breathing became embedded in my brain, and a couple of days later I sat down to compose a piece of music based on it, played at a tempo equal to my running pace, and built on a 1000-measure structure. At just over 43 minutes, the piece came out almost precisely as long as the time it took me to complete the race.

5mi is a single, uninterrupted piece of music consisting of 11 distinct musical sections. It was composed and recorded in its entirety in a single 4-hour session. The 11 track names are inspired by locations near the 5-mile race route, in my hometown of Austin, Minnesota.

The album is available on iTunes, Spotify, and other streaming/download services.

Side note: After the 5-mile race, I actually started to embrace the idea of running without headphones. At the end of October, I ran in my first ever half marathon. Just under 2 hours of running, with no music. I might even have to credit the lack of music with my good time. I found the chatter of the 9:10/mile pace runner so annoying that I sped up to get out of earshot from him, knocking my time down to a 9:06 pace and finishing the race in 1:59:05.

Dishonorable Mention: Yes — Heaven and Earth
This album most certainly is not in my top 5 for the year. But I still feel compelled to mention it here simply because it is so absolutely terrible. I have been a Yes fan for most of my life (ever since I first heard “Owner of a Lonely Heart” as a 9-year-old). Some of their music is my favorite music ever. I would put at least four of their albums in my “desert island selection” (The Yes Album, Fragile, Close to the Edge, Relayer). But they also have several mediocre albums (Tormato, Big Generator, Talk) and a few truly awful ones (Union, Open Your Eyes, Magnification).

The last album Yes produced that I thought was genuinely decent (if not actually good) was 1999’s The Ladder. In the past few years, the band has become a parody of itself, more Spinal Tap than Spinal Tap could ever have dreamed of being.

None of that prepared me for how awful Heaven and Earth is.

It took me months, and several attempts, before I could force myself to listen to the entire album, or even an entire song. Then I did it once and promptly deleted the album from my iTunes library. It is so bad, I want to forget that it even exists. I have never, ever found an album by any artist so absolutely appalling as this album. Although I have had only tepid enthusiasm for the albums I liked this year, Heaven and Earth definitely left the strongest impression on me. That’s worth noting in its own twisted way.

Top 5 Albums of 2014: The Finalists

Getting closer… I’ve eliminated a few albums that just don’t stand a chance *cough* *Heaven and Earth* *cough*. Here are the top 10 contenders for the final list. The final list finalists, if you will.

Aphex Twin — Syro
Beck — Morning Phase
Com Truise — Wave 1
Foo Fighters — Sonic Highways
Lusine — Arterial [EP]
Room 34 — 5mi
Tycho — Awake
U2 — Songs of Innocence
“Weird Al” Yankovic — Mandatory Fun
Zero 7 — Simple Science [EP]