Troubleshooting is a normal part of running a website, but sometimes issues can get a little weird. Here’s a problem that recently came up and the solution we were able to find for it:
The Problem: PDF Packing Slips and Missing Metadata in WooCommerce
Recently, I received a strange problem report across my desk – a client was saying that WooCommerce PDF Invoices & Packing Slips was giving their warehouse team a hard time. The issue was to the point that they were having fulfillment errors, returned product, and more. The Packing Slip was also only listing one of two Product Attributes and the warehouse crew needed both in order to pick the products correctly.
This was surprising as PDF Invoices & Packing Slips is usually considered a must-have plugin for any Woocommerce build that ships in-house just because of how useful it is. Packing slips will help you pack boxes more quickly, reduce packing errors, communicate professionalism to your customers, and nip “Is what’s in the box supposed to be what’s in the box?” problems in the bud. The kind of oversight that would cause issues with this plugin seemed unlike them and very unusual. But, was it the plugin or was it WooCommerce? That’s what I needed to figure out – and fast!
A Hypothetical Scenario for a Real Situation
As a courtesy to our client, I won’t be using any real product names, real product attributes, etc. While the scenario presented here may be hypothetical, the situation is very real and is very much so a source of hair-pulling frustration!
For the sake of example, let’s dig into one of my outside-the-office hobbies, tabletop wargaming, and pretend that the product in question is a great big “base terrain” mat with two Attributes:
- Attribute 1: “Pattern”, with options for Forest, Desert, and Winter
- Attribute 2: “Size”, with options for 48″x72″ or 60″x96″
Let’s also pretend that Winter is an all-new pattern, something our shop owner is all too happy to call attention to in the product title itself and updates the name to “Giant Wargaming Playmat, now available in Winter terrain”. Nothing too out of the ordinary, right? And yet, that’s all it takes for problems to occur.
The problem crops up when an Eager Wargamer places an order for the new Winter mat. In this scenario, the packing slip would look something like this:
The Size Attribute is listed, but the Pattern Attribute isn’t. That’s no good!
A Disclaimer About Unique SKUs
Naturally, the ideal way to handle situations like this from the warehouse employee’s point of view is for every variant to have its own, unique SKU. Unfortunately, the realities of fulfillment may not always allow for that, especially for resellers who are beholden to using the SKUs set by their suppliers. Also, from the customer’s point of view, they’re not usually going to know your SKUs, so a list of all the various choices they’ve made is beneficial to the end customer, even in cases where it’s not beneficial to the fulfillment staff.
The Discovery: Why Packing Slips Were Missing Metadata
By delving into the source code of the PDF Invoices and Packing Slips plugin, I came across this snippet in /templates/simple/packing-slip.php and /templates/simple/invoice.php:
<?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?> <span class="item-meta"><?php echo $item['meta']; ?></span>
On line 2 is the sneaky culprit. Innocuous looking, for sure, but using the hook right above it to run experiments, I discovered that the ‘meta’ array key on an Order Item contains the same information I’d get by calling get_formatted_metadata() on the Order Item itself. Although this was useful information, it also meant I wasn’t going to find answers in PDF Invoices and Packing Slips and would need to dig deeper.
Deep in the guts of WooCommerce, almost down at the bedrock, I came across the answer in /woocommerce/includes/class-wc-order-item.php. In that file is the following function:
public function get_formatted_meta_data( $hideprefix = '_', $include_all = false ) { $formatted_meta = array(); $meta_data = $this->get_meta_data(); $hideprefix_length = ! empty( $hideprefix ) ? strlen( $hideprefix ) : 0; $product = is_callable( array( $this, 'get_product' ) ) ? $this->get_product() : false; $order_item_name = $this->get_name(); foreach ( $meta_data as $meta ) { if ( empty( $meta->id ) || '' === $meta->value || ! is_scalar( $meta->value ) || ( $hideprefix_length && substr( $meta->key, 0, $hideprefix_length ) === $hideprefix ) ) { continue; } $meta->key = rawurldecode( (string) $meta->key ); $meta->value = rawurldecode( (string) $meta->value ); $attribute_key = str_replace( 'attribute_', '', $meta->key ); $display_key = wc_attribute_label( $attribute_key, $product ); $display_value = wp_kses_post( $meta->value ); if ( taxonomy_exists( $attribute_key ) ) { $term = get_term_by( 'slug', $meta->value, $attribute_key ); if ( ! is_wp_error( $term ) && is_object( $term ) && $term->name ) { $display_value = $term->name; } } // Skip items with values already in the product details area of the product name. if ( ! $include_all && $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) { continue; } $formatted_meta[ $meta->id ] = (object) array( 'key' => $meta->key, 'value' => $meta->value, 'display_key' => apply_filters( 'woocommerce_order_item_display_meta_key', $display_key, $meta, $this ), 'display_value' => wpautop( make_clickable( apply_filters( 'woocommerce_order_item_display_meta_value', $display_value, $meta, $this ) ) ), ); } return apply_filters( 'woocommerce_order_item_get_formatted_meta_data', $formatted_meta, $this ); }
Lines 26 through 29 are the source of our client’s woes. The Packing Slip template as written uses the function default – it doesn’t pass that all-important “Include All” flag, which, as you can see on Line 1, defaults to ‘false’. As a result, the If statement on line 27 tries to be helpful and it backfires in spectacular fashion.
What it’s doing there is attempting to match the Attribute Label, in this case the word “Winter”, against the product name, and then “helpfully” decides that listing the pattern attribute would be redundant on grounds that it’s right there in the product name. And, out the window it goes. This tells us why the metadata is missing and the following tells you how to fix it.
The Solution: How to Avoid Missing Metadata in Packing Slips in WooCommerce
There are multiple ways to avoid this particular problem. The right one for you will depend on your specific circumstances.
1. Avoid Attribute Labels in Product Names
This is probably the easiest way to fix the problem; if it doesn’t find the label, it won’t hide that Attribute. However, this can hurt you from a marketing standpoint if minor announcements such as “Now available in Green!” make sense for your marketing plan and increase conversions. Instead of changing product names, you could put those types of announcements in your site’s Hero area, but your theme may not be set up for such things.
2. Use Unique SKUs for Each Variant
As previously mentioned, using unique SKUs will allow the person fulfilling your orders to have zero ambiguity about what they’re supposed to put in the box. However, it doesn’t do much good for the end customer who will ultimately receive and read the packing slip to check they received everything they ordered; they aren’t going to know what SKU is what unless they go back to your site and start cross-referencing. Most customers won’t do that and may just decide to go with a different company that gives them what they need.
3. Template Modifications
If you’re not afraid to roll up your sleeves and do a little theme editing, PDF Invoices and Packing Slips provides the same kind of powerful theming capabilities that WordPress and WooCommerce do. By taking advantage of that and adding a few lines of code, it’s possible to get all the attributes to show up, regardless of their presence in the product name.
Problem Solved: Template Modifications
So, you want to go with Door #3? Creating your own PDF Invoices & Packing Slips templates is relatively easy and involves three steps:
- Add the files to your theme
- Customize them to your liking
- Tell the plugin to use your newly-created templates, not the built-in ones.
A quick note before we begin – any time during these instructions that I put something in square brackets, such as [your-theme-folder-here], that is not meant to be typed literally. Instead, substitute whatever is appropriate for your needs!
Step 1: Add the PDF Invoices & Packing Slips Template Files to Your Theme
When you log into your WordPress admin, navigate to the PDF Invoices settings page; you can find it under the WooCommerce menu item:
When you do, you’ll be greeted with a page that looks similar to the one above. We’ll be using this page multiple times over the course of setting up this customization, but, for the time being, what you’re looking for is the instructions under “Choose a Template”.
In there, it’ll list the plugin’s location for your WordPress install, as well as the location of your active theme. In most cases, this will be /wp-content/plugins/woocommerce-pdf-invoices-packing-slips/templates/Simple for the source, and wp-content/themes/[your-theme-name-here]/woocommerce/pdf/[template-name-here]”, respectively.
Please note that the plugin doesn’t call out that ‘yourtemplate’ can be whatever you want it to be; this means that you can even have multiple templates. For example, if you want do to a special fall-themed one for a big sale without needing to hack up your year-round one, you can do that.
Using the method you prefer, FTP, command line, theme editor, etc., create the folders the plugin is asking for, if they don’t already exist. In the example above, that would mean creating a ‘woocommerce’ folder within your theme folder, if it doesn’t already exist. Inside that, create a folder named ‘pdf’, and inside that, create a folder named whatever you will be calling the template. I used ‘All-Attributes’ as the name of the template, so my final path was /wp-content/themes/[theme folder here]/woocommerce/pdf/All-Attributes.
Once the folder is created, copy (not move!) all the files from the source to your new folder. At the time of this writing, there are five files: html-document-wrapper.php, invoice.php, packing-slip.php, style.css, and template-functions.php.
Step 2: Modify the Template
In the text editor of your choice, open the packing-slip.php (or invoice.php, or both) in your new template’s folder, and look for the following code. It should be in the vicinity of Line 87 in packing-slip.php, and in the vicinity of Line 100 in invoice.php. Alternatively, you could also search for ‘wpo_wcpdf_before_item_meta’, which appears exactly once in both files; the Item Meta <span> tag will be right below that hook.
<?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?> <span class="item-meta"><?php echo $item['meta']; ?></span> <dl class="meta"
Once you’ve located the section to be modified, replace the <span class=”item-meta”> line so that the snippet looks like this:
<?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order ); ?> <span class="item-meta"> <?php $do_ul = false; foreach($item['item']->get_formatted_meta_data('_',true) as $data){ if(!$do_url){ echo '<ul class="wc-item-meta">'; $do_url = true; } echo '<li><strong class="wc-item-meta-label">' . $data->display_key . ': </strong><p>' . $data->display_value . '</p></li>'; } if($do_url){ echo '</ul>'; } ?> </span> <dl class="meta">
Save the file, upload if necessary, and that’s it for the modifications! What the new code does is attempt to loop through any Order Item Metadata. If any exists, it creates an unordered list of the data it finds.
Step 3: Tell the Plugin to Use Your New Template
Go back to the PDF Invoices settings page, select your new template from the “Choose a Template” dropdown, and Save Changes. To see the results of your handiwork, generate an Invoice or Packing Slip (depending on which you modified). Success! Both Product Attributes are now present.