Changing the Auto-Suggest Behaviour in WooCommerce

A request came in this week from a client with a WooCommerce store:

I just placed an order through our website and had an issue with the state. The customer lives in Oregon, so I typed “O” hoping Oregon would auto populate, but it didn’t. I was stuck having to scroll through all 50 states to find Oregon.

An animated Gif showing WooCommerce's default autosuggest behaviour on select boxes.
Confusing for most people.

I tried it, and it’s true. Typing “o” doesn’t narrow the list down much, and the way it does narrow it down doesn’t make much sense in this context: The autosuggest on the State field matches the search phrase anywhere in one of the options, so typing “o” gets you a list of all states with the letter “o” in the state name, ordered alphabetically by state name. Go ahead and try it out in a WooCommerce demo shop.

Out of the box WooCommerce uses SelectWoo, which is essentially Select2 with more accessibility. Select2’s default behaviour of matching the typed text to any part of an option makes sense in many places, especially when searching a store or for categories, but for selecting states, (and countries, although slightly less), it’s confusing.

Nothing’s filterable, but we can set a default

Select2 lets us set the callback function used for matching, so we can customize the matching behaviour, but there’s a problem, the settings for SelectWoo are set in WooCommerce’s country-select.js file, and there’s no way to filter them, so we can’t change the matching behaviour on initialization. It turns out that once a Select2/SelectWoo instance is initialized there’s no way to change the settings either.

When I got to here I thought we were out of luck – either I’d be creating a child theme and re-implementing the checkout, or we were going to live with SelectWoo’s default filtering behaviour. Luckily, I found one way to change the matching callback: It’s possible to set the default SelectWoo options callback before any instances are initialized. So that’s what we’re going to do.

Server-Side

First we need to add a bit of Javascript to our checkout page, so we enqueue a small script that’s dependent on the selectWoo script.


namespace JB\WooAutosuggest;



function enqueue_frontend_scripts() {
    
    if( function_exists('is_checkout') && is_checkout() ) {

        wp_enqueue_script( 'jb-was-checkout-autosuggest', plugins_url( '../assets/checkout-autosuggest.js', __FILE__), array('selectWoo') );

    }
}

add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_frontend_scripts', 20 );

And that’s all the PHP we need! Because our solution changes the default matching behaviour we are careful to only enqueue this script on the Checkout page using WooCommerce’s is_checkout() function.

Client-Side

The enqueued Javascript file is pretty simple as well:

(function($) {
    // Based on custom matcher here: https://select2.org/searching#customizing-how-results-are-matched
    function start_matcher(params, data ) {
        
        // If there are no search terms, return all of the data
        if ($.trim(params.term) === '') {
          return data;
        }

        // Do not display the item if there is no 'text' property
        if (typeof data.text === 'undefined') {
          return null;
        }

        // `params.term` should be the term that is used for searching
        // `data.text` is the text that is displayed for the data object
        // Make sure to compare in the same case! 
        if ( data.text.toLowerCase().indexOf(params.term.toLowerCase() ) === 0 ) {
            return data;
        }
        // Return `null` if the term should not be displayed
        return null;
    }
    $.fn.select2.defaults.set('matcher', start_matcher );
})(jQuery);

There’s a closure so we don’t pollute the global namespace, then we define a new matcher function that matches the beginning of the phrase, and set it as the default matcher. This code runs as soon as it’s ready – it doesn’t wait for a DOMReady event, because by the time DOMReady fires the SelectWoo boxes have probably been initialized already.

Conclusion

I wish we didn’t have to set the defaults for the whole page, but that’s what we’re stuck with at the moment. We could apply different matching patterns to different pages by adding a few more conditions to the PHP block, and a bit more Javascript. This works to make the checkout page work as expected, though.

All the code is up on Github as a WordPress plugin. If it’s useful let me know!

An animated Gif showing WooCommerce's autosuggest matching the beginning of each option.
This makes more sense!

Adding Custom Fields to WooCommerce Products in 2018

Screenshot showing the Shipping section of WooCommerce's Product Data box.
Look, there’s a Shipping section!

This week a client asked to add estimated ship times to the single-product pages in WooCommerce. Ship times are sometimes different for different products, so adding it as a custom field to each product made sense. As a bonus, there’s a “Shipping” tab in the Product Data metabox that the field can go in. Some Googling led me to Tom McFarlin‘s “Adding Custom Fields to Simple Products with WooCommerce” tutorial on tuts+, which is pretty thorough, but out of date – the actions he’s are no longer in WooCommerce, so it’s time for a new version of that tutorial, my-style. There are three main tasks to accomplish: Add the field to the WordPress back-end, save whatever is in the field, and output the data on the Single Product page. We’ll look at each task separately. The completed plugin is up on GitHub if you want to follow along.

Add the Custom Field to WooCommerce in the WordPress Back-End

WooCommerce provides an incredible number of hooks and filters. We’re going to us woocommerce_product_options_shipping hook, because it runs in the shipping block, but is an equivalent hook for each Product Data tab. Look at the files in the woocommerce/includes/admin/meta-boxes/views directory to understand how the Product Data, (and all the WooCommerce Metaboxes), are created, and what hooks are available.

Now we know what hook we need, it’s time for some code:


function print_admin_field() {
    /**
     * @var \WC_Product
     * @see https://docs.woocommerce.com/wc-apidocs/source-class-WC_Meta_Box_Product_Data.html#42
     */
    global $product_object;

    if( $product_object->get_type() == 'simple' ) {
        $field = [
            'id'            => 'sif_ships_in',
            'label'         => __('Ships In', 'jb-sif'),
            'placeholder'   => __('example: 1-2 days', 'jb-sif'),
            'value'         => get_post_meta( $product_object->get_id(), '_sif_ships_in', true ), // Pre-fill any values that have already been saved.
            'description'   => __( 'Add a "Ships in X" message below the Product Meta on the single-product page.', 'jb-sif'),
            'desc_tip'      => true
        ];

        \woocommerce_wp_text_input( $field );
    }
}

add_action( 'woocommerce_product_options_shipping', 'JB\SIF\print_admin_field');

We make sure we have the $product_object variable available, (it’s set in the WC_Meta_Box_Product_Data class, linked with the @see directive), then we make sure we’re adding the field to a “Simple” product type. Then, instead of writing a bunch of HTML, we set some info in an array, and pass it to the woocommerce_wp_text_input() function, (with a backslash, because the plugin is namespaced). WooCommerce has several of these helper fields available that create a form fields and their associated markup so they fit nicely with the rest of the WooCommerce admin.

Notice we’re already getting a value with get_post_meta(). That’s because the same code gets used every time the field is displayed – so if there’s a value in the database we want to display it. This is all we need to properly display a field in the Shipping section of the Product Data. We could add a name attribute to the $field variable, but it defaults to the value of id, so I left it out. Setting desc_tip to true puts the description in the tooltip that appears when someone hovers the circle with the question mark beside the field.

The last line of the code block above hooks our function into the woocommerce_product_options_shipping action. Because the plugin has its own namespace we need to include the full name of the function in the add_action() call.

A screenshot of the new "Ships In" field in the WooCommerce admin.
Our new “Ships In” field, looking great.

Save Whatever is Entered in the Field

Again, a game of hooks figuring out how to save the data. Tom’s tutorial from last year used a woocommerce_process_product_meta hook which no longer exists. It looks like it’s been replaced by a woocommerce_process_product_meta_* hook for each product type. Since we’re only using a Simple product, we’ll use the woocommerce_process_product_meta_simple hook. Now we know which hook to use, some code:


function save( $post_id ) {

    // check nonce
    if( ! ( isset( $_POST['woocommerce_meta_nonce'], $_POST['sif_ships_in'] ) || wp_verify_nonce( sanitize_key( $_POST['woocommerce_meta_nonce'] ), 'woocommerce_save_data' ) ) ) {
        return false;
    }

    update_post_meta( $post_id, '_sif_ships_in', sanitize_text_field( $_POST['sif_ships_in'] ) );

}
add_action( 'woocommerce_process_product_meta_simple', 'JB\SIF\save' );

In the save() function, (which we can name “save” without collisions because we’re using namespaces), we check the field is part of the $_POST array, check the nonce set by WooCommerce, and save the contents of the field, without forgetting to sanitize the data first.

WooCommerce may already checks the nonce for us, but I am not 100% sure, so I added an extra check.

Output the Data on the Single Product Page

WooCommerce’s plethora of hooks make placing the output relatively simple. I chose to put it right below the “Add to Cart” button on the Single Product page, but if you look through WooCommerce’s templates/content-single-product.php file and the files in the templates/single-product directory you’ll find lots hooks to use – and of course you’re not restricted to the single-product page. If you want to add your output to each product in a list of products take a look in the templates/content-product.php file.


function print_output() {

    global $product;
    $ships_in = get_post_meta( $product->get_id(), '_sif_ships_in', true );


    if( !empty( $ships_in )): ?>
        
< ?php echo sprintf( __( 'Ships in %s.', 'jb-sif' ), $ships_in ); ?>
< ?php endif; } add_action( 'woocommerce_single_product_summary', 'JB\SIF\print_output', 45 );

Our print_output() function is pretty simple: Grab the post_meta, check it’s not empty, and output it mixed in with a “Ships In” string. It’s hooked into the woocommerce_single_product_summary action with a priority of 45, which means it appear after the Add to Cart button and after the Product Meta. WooCommerce’s template files are really good at explaining what functions are hooked to actions, and the priority they’re hooked at, which makes it easy to place things on a page without having to copy templates over to your theme.

A screenshot showing the output of the "Ships In" field on the single-product page.
Here you can see the output of the new “Ships In” field on the single-product page. The product meta was moved elsewhere.

Conclusion

Adding fields to the WooCommerce part of a WordPress admin area is much easier than adding a whole metabox. You can grab the completed plugin on Github. You’ll see there’s a bit more to the plugin: requirements are checked using Mark Jaquith’s method before embarking on a namespaced plugin with shorthand array syntax.

If this is useful let me know, and feel free to open tickets and send pull requests on Github.

Flush Opcache with Varnish: a WordPress Plugin Announcement

Flush Opcache with Varnish is here to rescue you from the constant annoyance of flushing yet another cache!

If, like me, you use PHP’s Opcache to speed up a site, and you have a Varnish cache, and maybe some other server-side caches, you probably want to flush the opcache, Varnish cache, and any other server-side caches at the same time after changing the PHP files on your server, (like, when you update WordPress, plugins, or themes). I don’t want to click a button for each cache type, or worse, have to log in to the command-line for a sudo service restart varnish after doing updates, especially if I’m doing repeated updates. Flush Opcache with Varnish hooks into Mika Epstein’s excellent Varnish HTTP Cache plugin and flushes the PHP Opcache and the WP Super Cache cache, (if you use WP Super Cache), every time you manually flush the Varnish cache, turning the Varnish HTTP Cache “Clear Cache” buttons into a three-for-one deal.

If you also use the plugin WP Opcache to manage your opcache then WP Opcache will be called to do the actual Opcache flushing so you can take advantage of its automatic rebuilding of the opcache.

I’m already using Flush Opcache with Varnish in production and it’s saving me time every time I update a theme or plugin. You can get it in the WordPress.org plugin directory.

Why X Theme and Pro sometimes cause PHP warnings on WPEngine

The Problem

Earlier this fall I was working on an e-commerce site that’s hosted on WPEngine that used Themeco’s Pro as its parent theme most pages hadn’t seen any customization yet, most of the code was stock, and this appeared on the Products page:

PHP Warning:  Invalid argument supplied for foreach() in wp-content/mu-plugins/wpengine-common/plugin.php

Hmm. Who is passing what to a foreach?

It turns out that both X Theme and it’s progeny Pro do this turn off WordPress’s automatic responsive images:

// Disable WordPress 4.4 Responsive Images
// =============================================================================

if ( ! function_exists( 'x_disable_wp_image_srcset' ) ) :
  function x_disable_wp_image_srcset( $source ) {
    return false;
  }
  add_filter( 'wp_calculate_image_srcset', 'x_disable_wp_image_srcset' );
endif;

They’re hooking into the wp_calculate_image_srcset filter and returning false, which stops WordPress from adding a srcset attribute to an image, (more on why in a moment). The problem here is that the value being passed to the filter, $sources, is an array, but x_disable_wp_image_srcset() turns that array into a boolean false, so then anything else hooking into wp_calculate_image_srcset, for example, WPEngine’s CDN system, tries to iterate over the $sources array, but it’s not an array, hence the error. By filtering $sources to a boolean false Themeco is breaking the expected, and documented, behaviour of the wp_calculate_image_srcset filter.

Solutions

Luckily, there are two easy fixes for this problem. The first is to make sure you’re using the latest version of WPEngine’s must-use plugin. They’ve caught on to the problem and check to make sure $sources is actually an array:

if ( is_array( $sources ) ) {
    foreach ( $sources as $source ) {
        // do the stuff
    }
}

If you can’t do that, at least Themeco made the function pluggable, so you can add your own version of x_disable_wp_image_srcset() to your theme’s functions.php or a plugin that returns an empty array and plays nicely with other plugins:

// This is a pluggable function. If we don't define it here then Pro does in 
// pro/framework/functions/global/admin/thumbnails/setup.php
function x_disable_wp_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
    return [];
}

Or, you could use responsive images.

Confusion, (aka, why this happens in the first place)

When I first discovered this problem I E-mailed Themeco to tell them about the problem, however they pointed me at the documentation for the wp_calculate_image_srcset() function, which states the function can return false, and sent me on my way:

Hey John,

Thanks for writing in. In looking over WordPress' official documentation for that function/hook, I believe that boolean false should be the correct value to return:

https://developer.wordpress.org/reference/functions/wp_calculate_image_srcset/

In the "Return" section you'll see that it is supposed to return a string or a boolean false if there is an "error or only one source exists." It seems as though WPEngine should need to revise their function to account for these instances anyway even if not for X or Pro (and at least work to catch any fatal errors like this).

Hopefully that helps, cheers.


Best Regards,
Your Themeco Team

This confusion occurs because the convention in WordPress is that if a filter and a function have the same name, the filter filters the output of the function. However, in this case the convention is broken – when I raised the issue in the Advanced WordPress Facebook group a very experienced WP developer made the exact same mistake and assumed that allowable return types for the function should be fine to return from the filter. After we cleared up the confusion I filed a bug about the confusing names, go star it so it gets some attention!

A Fluke of PHP

So why does returning false in the wp_calculate_image_srcset filter stop WordPress from using responsive images? Here’s the relevant code from media.php:

$sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );

// Only return a 'srcset' value if there is more than one source.
if ( ! $src_matched || count( $sources ) < 2 ) {
    return false;
}

To follow along, if $sources is turned into a boolean false at the apply_filters() call, then a count( false ) happens in the condition on the next non-comment line. Instead of an error like I expected, running count() on any non-array value returns 1, so count( false ) == 1, also count( 0 ) == 1

Conclusion

First, be a good citizen of the WordPress ecosystem and don’t change the type of a filtered value to something other than the documented types for that value. Second, if you have any non-core themes or plugins installed you can’t trust that a value coming into a filter will be the type that the docs say, so check! When WPEngine realized there was a problem an is_array() fixed the problem. Third, read support E-mails thoroughly and make sure you understand them, it hurts when I go to the effort to document a problem and come up with a solution, and I’m told it’s not a problem. Finally, go +1 my ticket so maybe others won’t get bitten.

PhpStorm Review

Late last year I kept hearing a lot about PhpStorm 8, especially from WordPress people and wanted to try it out but the price was stopping me, (it turns out the price is probably worth paying – but it’s hard to know if the price is worth paying until you’ve already paid the price). Luckily for me WP e-Commerce has some licenses for core contributors, and apparently I count, (JetBrains, the people who make PhpStorm, provide licenses to open-source projects at no charge – thanks!).

One of the things that JetBrains asked in return for the licenses was that we try to write reviews of our experience, and after many months of using PhpStorm, here are my thoughts.

PhpStorm crashed several times in the 24 hours after I installed it, not a good start. Support suggested installing the EAP version, which is like a beta stream, which fixed the problem. Since then I’ve switched back & forth between the current release and EAP versions of PhpStorm, but I usually use the EAP, because I like shiny things.

Performance

I had heard that PhpStorm was fast, but it’s a Java app, (like Netbeans or Eclipse), so I had my doubts. When first installed there were some pretty big performance issues, especially related to scrolling. PhpStorm was exhibiting what I refer to as “that Java scroll lag” – one of the reasons I tend to avoid Java apps. There was also a problem that when scrolling with the mouse the text would disappear, which makes mouse scrolling nearly useless. Eventually I learned that there’s a version of PhpStorm for OS X that comes with a bundled, tweaked, Java Run-time Environment, and using that version of PhpStorm solved these problems.

When comparing PhpStorm’s performance to Sublime Text, (2 or 3), Sublime Text is still way faster. PhpStorm feels like a normal app, but ST3 has that amazing snappiness that Sublime Text users have come to love, and want to see everywhere. While PhpStorm’s performance is satisfactory, it’s not as blazing fast as the chatter led me to believe, but the combination of decent speed, and the other benefits that of using a real IDE, mean it helps me write cleaner code faster.

Setup

Like most coding programs, especially Java ones, PhpStorm hurt my eyes out of the box, but the look is tweakable enough to make it look how I want. There are two built-in themes, (“Default” and “Darcula”), and you can load 3rd-party colour schemes for the text editor. I think it might be possible to load 3rd-party themes as well but I haven’t investigated this. Switching to the Darcula theme, and switching to the Predawn colour scheme, (I can’t remember where I found the PhpStorm port, maybe it even came with it), and Inconsolata made PhpStorm feel like home.

Coming from ST3 I re-mapped some keyboard shortcuts to match ST3 make the transition easier and make it easier to switch between the two when needed.

I had to download extra drivers to connect to MySQL, which was kind of annoying, but I suspect might be license-related.

And Java. PhpStorm 8 requires Java 6, which is no longer installed on Macs because it’s so old, that’s why there’s a version offered for OS X with a bundled JRE. It’s possible to install Java 6 with a package from Apple, because we all love having several versions of Java installed, or it’s possible to tweak some plist files to let PhpStorm run with Java 8, (the current version). Fortunately, it looks like PhpStorm 9, (which I’m running the EAP of now), will do away with this requirement.

Code Inspection

This is an IDE, so it’s aware of any databases that get used. This is great most of the time, (the code inspector tells you if you mis-type a column name, for example), but the it also fusses when I build big SQL queries by concatenating strings. So far I’ve just ignored these complaints.

If I include a file like this in WP: include( get_stylesheet_directory() . ‘/myfile.php’); PhpStorm thinks that myfile.php doesn’t exist, even if it does. Since this is the “right way” to include files in WP themes, and PhpStorm advertises built-in WP knowledge, PhpStorm should understand it.

When making a commit to your preferred Source-Control system, (Git, right?), PhpStorm inspects the code you’re committing, and if there are errors or warnings it pops up a window saying so, and gives you a chance to fix these problems. This is a great way to slowly clean up the code of older projects with lots of legacy, (read: “written before I knew better”), code. However, it’s also kind of annoying when unfixable errors or warnings are included, like when I’ve concatenated together an SQL query.

I was working on a plugin that works with WooCommerce and PhpStorm complained that I was assigning the return value of a function that returns void to a variable. It turns out that WooCommerce has several PHP DocBlocks that state a function returns void when it actually returns a value. Thanks to a WP e-Commerce copy of PhpStorm for pointing out errors in WooCommerce!

Annoyances

If someone from Jetbrains reads this, consider these bug reports!

text-gone-bonkers

Sometimes the fonts go bonkers. When I was first setting PhpStorm to use Inconsolata they did, (that was using the release version), and a few times on the EAP I committed some code, and when the commit finished the display went crazy again.

PHPStorm Power Consumption

Power consumption – I’m not sure how it compares to Sublime Text, but it’s consuming more power than Photoshop today – #2 on the list behind Safari with 11 tabs open.

 

The first time I used the “Pull Up” refactor tool, which lets you pull methods up from a class into the parent class, I pulled up a bunch of static methods. PhpStorm didn’t actually go and find the places where the methods were called and change the calls, which I thought was the point of the “smart” IDE. Inheritance would mean that these methods would still be available, but there’s no reason to load the child class if everything I need is in the parent.

The auto-complete can be really bizarre. Often doesn’t pick the logical thing, or even the first thing in the suggestion list. For example, if I type “col” then press tab or enter autocomplete completes as “columns” not “color,” even though color is the first suggestion. It even insists on completing as “columns” if I type “colo” and press tab/enter. This drives me mad.

Search for any file, (I’ve got it mapped to Cmd+P to match Sublime Text): If a project has several files with the same name there doesn’t seem to be mapping that makes sense. To me it would make sense for the first option to be the one I used the most recently, or the one “closest” to whatever file I’m already looking at in the directory structure. If the files are sorted at the moment, it looks like it’s alphabetical, which should change.

Auto-change formatting: I try to use WordPress style in most of my PHP & Javascript – especially for WordPress-related work! WP style suggests putting a space around function parameters in both Javascript and PHP. However, if I’m doing chained calls in Javascript PhpStorm removes the space after the last function argument when I press the . key. For example:

$( 'body' )
// becomes
$( 'body').
// as soon as the period is typed

When PhpStorm 8.0.3 came out I started getting SSL error messages on startup. Apparently there’s a problem in some SSL-related part of the bundled JRE. 8.0.3 came out on February 13 and there’s still no fix. The current EAP doesn’t have this problem.

PhpStorm’s licensing scheme for open-source projects has changed this year, so I need a Jetbrains account, the licence is issued from WP e-Commerce to that account, then I’m supposed to sign in to the account with PhpStorm and it’ll activate. Unfortunately the SSL bug prevents PhpStorm from talking to the Jetbrains servers, so I can’t activate my PhpStorm license. Luckily the EAP comes with its own license, otherwise I would be out of luck. A bug that prevents customers from activating software is a very big bug.

Conclusion

PhpStorm is good, very good at some things, especially the IDE-type functionality I was looking for. Integrated code inspections & linting were a welcome surprise that is really helping my code look better and encouraging me to write better in-line documentation. There have been problems, though, that might have caused me to abandon PhpStorm I hadn’t pre-decided to give it a real chance. I nearly always use the EAP both because I like shiny things, and because the bugs in the release version of the software are annoying, or serious, enough to drive me into the arms of the EAP.

So, is PhpStorm worth the purchase price? Probably. It handles code completion, data-source awareness, most refactoring, and all its IDE-related functionality quickly and as promised, (or very nearly so). The problems tend to be in other areas, and from the state of the current PhpStorm 9 EAP, most of these problems will be solved soon.