Category Archives: WordPress

WooCommerce – Custom Product Fields and WooCommerce Fields Factory

WooCommerce does have ‘attributes’ available for products but like most things in WC these are tailored for users selling clothing and various other consumer products. In my application each product is unique and once sold gone forever. Each product has common characteristics though, such as year of manufacture, metal it is made from, and where it was manufactured. As a matter of course the user of the WooCommerce store wanted to define a set of characteristics that would be entered when a new product was created and then wanted those characteristics displayed in the product listing. A good solution was arrived at through the use of the WooCommerce Fields Factory plugin and modifying the Storefront Single Product->Tabs->description.php template.

The WC Fields Factory plugin allows you to add custom fields to your WooCommerce products. It has the added benefit of allowing you to display those fields on the admin side where they can be edited at the product level by your shop admins. Nifty. In my application I created a group of Admin Fields called “Coin Attributes” and then populated the group with the fields that the customer wanted for their products.

Once that was done and I made sure to check the Hide in Product Page and Hide in Cart & Checkout Page for each field. Now when I add a product I see this in the General tab for each product:

So now we can store the data with each product as needed. The next step is to make sure the data displays as required for products. The key is that we only want the data field to display if there’s a value set. To do that we just need to do a bit of tweaking to the Storefront Single Product->Tabs->description.php template. We do that by making a copy of it into the /woocommerce/single-product/tabs folder of our child theme. In our case we just wanted to display the data in a table. So the PHP code required looked like this:

    //get the custom field data using the get_post_meta function.  All WC Field Factory fields are prefixed with wccaf_ and then the field name
    $coin_type=get_post_meta( $post->ID, "wccaf_coin_type", true );
    $krause_number=get_post_meta( $post->ID, "wccaf_krause_number", true );
    $mint=get_post_meta( $post->ID, "wccaf_mint", true );
    $mintage=get_post_meta( $post->ID, "wccaf_mintage", true );    

//display information only if there's a value set
    if (strlen($coin_type)>0)
        $coin_information.='<tr><td class="coin-information"><strong>Coin Type:</strong></td><td class="coin-information">'.$coin_type.'</td></tr>';
    if (strlen($krause_number)>0)
        $coin_information.='<tr><td class="coin-information"><strong>Krause Number:</strong></td><td class="coin-information">'.$krause_number.'</td></tr>';    
    if (strlen($mint)>0)
        $coin_information.='<tr><td class="coin-information"><strong>Mint:</strong></td><td class="coin-information">'.$mint.'</td></tr>';         
    if (strlen($mintage)>0)
        $coin_information.='<tr><td class="coin-information"><strong>Mintage:</strong></td><td class="coin-information">'.$mintage.'</td></tr>';  

    if (strlen($coin_information)>0)
        echo '<table class="coin-information">'.$coin_information.'</table>';

Of course where exactly you echo out the data in the TABS template is up to you and determined by how your product pages are laid out.

WooCommerce – Display SOLD Instead of Price for Sold Products

If you’re using WooCommerce and selling one-off products you don’t necessarily want to use the default terminology for sold out products. You might want to hide the sold price and remove the WooCommerce “In Stock” messages that are better suited to sites selling t-shirts and so on. Removing the stock availability messages is simple enough using the woocommerce_get_availability filter.

    add_filter('woocommerce_get_availability', 'my_get_availability', 1, 2);

    function my_get_availability($availability, $_product)
        $availability = '';

Couldn’t be any simpler, just set the availability text to an empty string.  Presto chango those messages are gone.

Replacing the price with SOLD when a product is sold is almost as simple, it just requires hooking into a couple more filters.

    add_filter("woocommerce_variable_sale_price_html", "my_remove_prices", 10, 2);
    add_filter("woocommerce_variable_price_html", "my_remove_prices", 10, 2);
    add_filter("woocommerce_get_price_html", "my_remove_prices", 10, 2);
    function my_remove_prices($price, $product)
        if (!$product->is_in_stock())
            $price = "SOLD";
        return $price;

WooCommerce – Add Phone Numbers to Header of Storefront Theme

I happen to like web stores that have contact details in prominent locations so it’s useful to be able to add phone numbers to the header of a site using WooCommerce. In my particular application I’m using the Storefront theme.  Firstly you need a  bit of CSS in your child theme’s style.css file. Your widths might be different and I’ve adjusted the margin of the site-search div to compress things a bit.

.header-contact-details {
    width: 50.7391304348%;
    float: right;
    margin-right: 0;
    clear: none;


Then you can use the storefront_header action to insert some code into the header.  To set the priority of the insertion have a look at the storefront header.php file.  In  my case I wanted the new HTML in between storefront_product_search and storefront_primary_navigation_wrapper so I set the priority to land in between those two.

//add phone numbers to header

    add_action('storefront_header', 'storefront_add_header_phone', 41);

    function storefront_add_header_phone()

        <div class="header-contact-details" style="text-align:right"><p style="color: rgb(64, 48, 74);font-weight:600;font-size:20px;"><i class="fa fa-phone" style="color:rgb(155, 126, 230)"></i> 555 555 555 (AUS)  <i class="fa fa-phone" style="color:rgb(155, 126, 230)"></i> +61 555 555 555 (INTL)</p></div>

The code is pretty clear, I make use of the font awesome font set to have some pretty phone icons and colour them to match the rest of the site style. Of course there’s a little bit of CSS inline here that could be moved into the style.css file but for now it works and I am happy with that. Here’s the what the end result looks like:

WooCommerce – Receive Copies of Customer Emails

If a user is new to WooCommerce it can be a little confusing what emails a customer is receiving and exactly what events are triggering the delivery of emails. To help alleviate the problem it can be useful to deliver copies of all customer emails (see the WooCommerce Email Notifications screen below) to another email address so they can be monitored.

WooCommerce Email Notifications Screen

In this case the woocommerce_email_headers() filter is useful. It allows you to intercept the email header and inject your own custom headers of the same form used by the PHP mail function. In my case I was happy to include a CC email address so I used this code:

//Send copies of customer emails to
add_filter( 'woocommerce_email_headers', 'copy_customer_emails_headers_filter_function', 10, 2);

function copy_customer_emails_headers_filter_function( $headers, $object ) 
    if ($object == 'customer_completed_order' || $object == 'customer_invoice' || $object == 'customer_note' || $object == 'customer_on_hold_order' || $object == 'customer_processing_order' || $object == 'customer_refunded_order') 
        $headers .= "CC: Someone <>" . "\r\n";

    return $headers;

The code itself is fairly self explanatory. It checks the passed object and if it’s one of the customer notification emails then the “CC: Someone <>” . “\r\n” string is appended to the email headers. That email address will now receive a copy of all the emails sent to a customer. Of course the customer will see the CC address in their emails so you could include a BCC address with something like “Bcc: Someone<>\r\n”.

WooCommerce – Default Manage Stock and Sold Invidually Values

I’ve had the somewhat dubious pleasure of launching a new WordPress / WooCommerce site for an established collectables business. The site replaced an aging cart system, that was frankly a steaming pile. Almost every item sold by this business is unique so they wanted to default the values for Manage Stock to true and Sold Individually to true.

The WooCommerce Inventory Screen

It turns out the easiest way to do this is to add some javascript to the admin_enqueue_scripts action that checks the form elements we need checked. Poking about with the Chrome element inspector at the WooCommerce stock handling form told me we needed the _manage_stock and _sold_individually checkboxes ticked and to set a value in the _stock text field. Here’s the code I put in my child theme’s functions.php file.

//set default stock handling values

    add_action('admin_enqueue_scripts', 'wc_default_variation_stock_quantity');

    function wc_default_variation_stock_quantity()
        global $pagenow, $woocommerce;

        $default_stock_quantity = 1;
        $screen = get_current_screen();

        if ( $pagenow == 'post-new.php' && $screen->post_type == 'product')
            <script src="//"></script>
            <script type="text/javascript">
                jQuery(document).ready(function () {
                    if (!jQuery('#_manage_stock').attr('checked')) {
                        jQuery('#_manage_stock').attr('checked', 'checked');
                    if ('0' === jQuery('#_stock').val()) {
                        jQuery('#_stock').val(<?php echo $default_stock_quantity; ?>);
                    if (!jQuery('#_sold_individually').attr('checked')) {
                        jQuery('#_sold_individually').attr('checked', 'checked');


The code itself is fairly simple. The javascript is injected into the document.ready function but only if we’re adding a new post and the post type is WooCommerce’s product post type. The appropriate checkboxes have their checked attribute set to true and the _stock field is set to a value of 1 if it’s currently zero. Simples!

wpDataTables Filters in WordPress

I’ve started using wpDataTables for data driven tables in WordPress. The ability to have a table linked to an Excel spreadsheet is neat and the automagically formatted tables that use the jQuery Data Tables plugin are nice too. I needed to tweak few things though. First, center align the table titles. Easy enough using the wpdatatables_filter_rendered_table


function my_filter_rendered_table( $table_content, $table_id )
	$content=str_replace('<h2>','<h2 style="text-align:center;">',$content);
	return $content;

The second issue I was having was with URL Link Columns. These are driven from an Excel spreadsheet by separating the URL and the link text with two pipe (|) characters. Something like:||Nifty Link Text

In the case of the data I was displaying some of the cells would need links and some wouldn’t. Trouble was wpDataTables appears to render every value in a URL Link Column as a link whether you pass it a URL or not. In fact, it uses the Link Text as the URL if you omit the URL. Instant broken links, yuck. No problems, I thought initially, I’d just use the provided wpdatatables_filter_link_cell filter to grab the URL links, parse out the URL and the link text and just return the link text if the URL was invalid. Pretty easily done with a bit of regex like:

	$url=preg_match('/href="(.*?)"/', $link, $url_matches);
	$link_text=preg_match('/>(.*?)<\/a>/', $content, $link_text_match);

It worked beautifully for the Link Text but rather frustratingly preg_match stubbornly returned 0 (no match) for the URL no matter what I tried. Even when I VAR_DUMP’ed the link it appeared that the preg_match should work. But it didn’t. In the end I used substr to echo back the link one character at a time and hey presto, wpDataTables was wrapping the href parameter in a single quote rather than a double quote. When I echoed the value to output using VAR_DUMP the browser (un)helpfully replaced the single quotes with double quotes. Grrrr. Anyway here’s the code that worked:


function my_filter_link_cells($link)
	$url=preg_match("/href='(.*?)'/", $link, $url_matches);
	$link_text=preg_match('/>(.*?)<\/a>/', $content, $link_text_match);
	if (strpos($url_matches[1],"/")===false || strpos($url_matches[1]," ")!==false)
	return $content;

Now my tables displayed links for values that actually had a proper URL and just text for those that didn’t. Yay.

wpDataTables seems pretty good, it isn’t free but $29 seems like a decent price for something that has worked pretty much exactly as advertised and has a tonne of features. Recommended.

Putting the Search Box into the Primary Menu in the Virtue Theme

I used the free Virtue theme in a recent contract and wanted to put the WordPress search box into the primary navigation menu. That was done easily enough by putting this into the child theme functions.php file:

function add_search_form_to_menu($items, $args)

    if( $args->theme_location != "primary_navigation" )
        return $items;

    return $items . '<li class="my-nav-menu-search">' . get_search_form(false) . '</li>';

However, the site I was building needed a private area for members and I’d been tinkering with the WordPress Access Control Plugin (WPAC) to accomplish that. The plugin allows you to display different menus to users who are logged in and those who are not. One menu (called Top Menu) was setup by WPAC to display as the Primary Navigation for users who were not logged into WordPress. While another (I called Top Menu – Private) was setup to display to members only. Obviously I wanted the WordPress search box to display on that menu too so I had to modify the above PHP slightly to allow for the private menu. Here’s how that worked:

function add_search_form_to_menu($items, $args)
    if( ($args->theme_location != "primary_navigation") && ($args->theme_location != "primary_navigation_wpac") )
        return $items;

    return $items . '<li class="my-nav-menu-search">' . get_search_form(false) . '</li>';

So there you have it. How to get the WP search box to display in the primary navigation menu when using the Virtue theme, and when using the Virtue Theme with the WordPress Access Control Plugin.

Redirecting Adwords by Operating System

My desktop software products run on Windows. I spend a significant amount each month on Google Adwords to drive traffic to my websites, which is problematic as Adwords does not allow you to filter traffic by operating system. It does allow you to reduce bids on Mobile traffic but it bundles Desktop/Full Sized Tablet with Browser traffic into one segment. In the last few years the amount of non Windows OS traffic I receive via Adwords has grown steadily and now makes up slightly more than 50%. I’ve been struggling for a while to work out what to do with this traffic as clearly users from non Windows devices cannot use my software. Yesterday at 4AM I hit on a possible solution and spent the day implementing it. Basically I’m now intercepting the clicks on my “Download Now” buttons/links, checking the client operating system, and if it’s not a version of MS Windows then redirecting them to a page targeted at my online SAAS products. Those products WILL work on their non Windows devices. Sure, it’s an obvious thing to do but I work in isolation and sometimes it takes me a while (years) to reach the “obvious” solution to a problem.

Here’s how I did it:

1) Installed the PGWBrowser plugin for jQuery. This plugin allows you to detect the OS, browser and viewport of web clients.
2) Enabled the plugin in WordPress by enqueuing the JS file:


For non WordPress pages (my Adwords landing pages) I used the following (making sure it came after loading jQuery):

<script type="text/javascript" src="/wp-content/themes/Divi-child/js/pgwbrowser.min.js"></script>

3) Now I adjusted the onclick call on my download buttons. I know that using the onclick event like this is archaic but I’ve been doing it this way since 2004 and it works nicely so I’m not changing it! I use this event to redirect users who download the software to a “Download Complete” page that allows me to count downloads. Here’s what the HTML/JS looks like now:

<a class="button_class button_icon_download" href="/downloads/some_file_name.exe" onclick="SetUpRedirect('some_file_name.exe',event)">

You can see I’ve passed the file name to be downloaded to the JavaScript function as well as the event (click) object.

4) The final step was to adjust the SetUpRedirect JS function to accomplish my goals. Here’s what that looks like now:

function SetUpRedirect(destination,event_object)
		var pgwBrowser = jQuery.pgwBrowser();
		var os=pgwBrowser["os"];
		var os_name=os["name"];

		if (os_name.indexOf("windows")>-1)
			return true;		

That’s all pretty self explanatory. We’re making use of the PGWBrowser plugin to get the Operating System of clients and checking if it contains “Windows”. If it does those users get redirected to the download success page as usual. However, if they are not Windows users we use the jQuery preventDefault method on the event object to stop those users downloading the trial version of my software. They are then redirected to another landing page that says something like “hey we noticed you’re using a non Windows device, why not try out our spiffy web-based product instead?” Neat.

Replace Post Excerpts with the Yoast META Description

Someone sent a question through this site to me yesterday.

Hi Mark,
Thanks for your site. I’ve been using yoast meta descriptions since beginning my site (800 posts) and now want to use the post excerpt in archives. Is there a way to use the yoast meta desc. as the wordpress excerpt? I find documentation on using excerpts as meta, but not the other way around.
Thanks for your help,

If you’re wondering Yoast SEO is the most popular WordPress SEO Plugin and it allows you to override a lot of default WordPress post values to better optimise your on-page SEO. If you’re wondering what a META description is then you should read this article on Wikipedia.

As it turns out, the solution isn’t too hard. First, check the WordPress documentation to see if there’s a filter that can be used when the post excerpt is retrieved (there is). Second, have a poke around the wp_postmeta MySQL database table and check what meta_key Yoast uses to store his post META description. Turns out that key is _yoast_wpseo_metadesc. Once we know that it’s simply a matter of writing a couple of lines of PHP and dropping it into the functions.php file:

add_filter( 'get_the_excerpt', 'replace_post_excerpt_filter' );

function replace_post_excerpt_filter($output)
        $output=get_post_meta(get_the_ID(), '_yoast_wpseo_metadesc', true);
        return $output;

In the first line we’re adding the get_the_excerpt filter and in the third adding the new filter procedure. The procedure itself is fairly simply, the get_post_meta function allows us to pull the Yoast META description for the post. Then we’re replacing the excerpt passed to the function with the Yoast value.

Of course this is going to replace the post excerpt everywhere. If you just wanted to change the value in your WordPress archives then you could just edit your child theme’s archive.php file (assuming your theme has an archive.php file). Just look for the main loop and some code that looks something like the_excerpt(); and replace it with:

echo get_post_meta(get_the_ID(), '_yoast_wpseo_metadesc', true);

GZIP Compression and WordPress

I had an issue back in November 2014 trying to enable GZIP compression with WordPress. A spare half an hour was found last week that allow me to solve the problem. I resorted to a .htaccess file change. I simply added this to the top of the .htaccess file:

<ifmodule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript

Now the WordPress site is being delivered to clients in GZIP format. This results in a significant speed improvement in page rendering as well as a significant decrease in the bandwidth my server needs to deliver. Furthermore, it’s superior to the ob_gzhandler method suggested by WordPress themselves because CSS and javascript files are compressed as well as the HTML. The ob_gzhandler method appears to only compress the HTML.