Bulk Pricing Breakdown

While many online stores, such as clothing retailers, tend to sell individual items there are those businesses that deal in part or even exclusively with bulk items instead. They might for instance have a product where the minimum amount that can be ordered is 100 units. Examples might include:

  • Hardware stores selling pre-defined quantities of screws or nails
  • Catering supply businesses selling large numbers of disposable knives and forks
  • Other businesses selling mass produced items to retailers

In this tutorial I am going to look at a scenario where the merchant is using Shopp[1] to power their online store and where they have set up bulk items by leveraging product variants. To simulate this I created a product called Door Knockers which I am hypothetically selling to hardware stores, letting them order palettes loaded up with 100, 250 or 500 door knockers at a time.

That’s important because we aren’t letting customers specify arbitrary quantities in this scenario – we are only allowing them to buy 100, 250, 500 or whatever quantities we define.

Screenshot of a demo product, using variants for preset bulk quantities

An example I set up with a variant called Quantity, allowing door knockers to be ordered in quantities of 100, 250 or 500.

This is all very easy to set up. Simply create or edit the product in question then enable and set up a new variant (in my case I have called it Quantity) then name them accordingly. Simplicity is crucial for what we are going to do in this tutorial and here are the rules you will need to follow for every product where you set this up: [Read more...]

Wrapping Widget Titles

I was doing a little customization to a site today and one of the effects I was to put in place meant I really needed to surround the widget title with an extra span or div in order to apply a little CSS magic. This is fairly straightforward and the following snippet can be added to the theme functions.php file to make it happen:

// Alter the widget title markup
add_filter('dynamic_sidebar_params', 'wrap_widget_titles', 20);

/**
 * Wrap the widget titles - including any existing before/after title markup
 * inside an extra div we can target with div.widget_title_wrapper.
 *
 * If we needed to do this selectively we could test against $widget['name']
 * or $widget['id'].
 */
function wrap_widget_titles(array $params) {
        // $params will ordinarily be an array of 2 elements, we are interested
        // only in the first element
        $widget =& $params[0];

        // Wrap the title
        $widget['before_title'] = '<div class="widget_title_wrapper">'.$widget['before_title'];
        $widget['after_title'] = $widget['after_title'] .'</div> ';

        return $params;
}

Et voila, we now have an extra box surrounding the title – which we can target with something like div.widget_title_wrapper – letting us apply a few extra effects.

Single Month List View

By default The Events Calendar’s upcoming events view is designed to list all events from “today” until whenever the last event in the calendar is.

What seems to be quite a common request though is to simply use the upcoming events page as a nice list view showing a specific month’s events. I’ve seen a few approaches to this but I thought I’d jot down this quick hack that allows for URLs like mycalendar.com/events/upcoming/2013-12 and then lists only events for that month and year.

function get_specified_month($uri) {
        // Look for patterns like 2099-12 at the end of the URI
        $month_pattern = '[0-9]{4}-[0-9]{2}';
        $optional_slash = '/?';
        $pattern = "#($month_pattern)($optional_slash)$#";

        // Check for matches
        $match = array();
        if (preg_match($pattern, $uri, $match) !== 1) return false;

        // We are interested only in the year/date portion
        return $match[1];
}

function upcoming_single_month_view(WP_Query $query) {
        // We are only interested in requests to view upcoming events
        if ($query->query_vars['eventDisplay'] !== 'upcoming') return;

        // Has a specific month been specified?
        $month = get_specified_month($_SERVER['REQUEST_URI']);
        if ($month === false) return;

        // Set the start date, end date
        $query->query_vars['start_date'] = $month.'-01';
        $query->query_vars['end_date'] = $month.'-31';
}

// We'll run our filter later than TribeEventsQuery::setArgsFromDisplayType()
add_filter('parse_tribe_event_query', 'upcoming_single_month_view', 20);

It really is a hack and I’ve paid no heed whatsoever to scenarios like a given month with a large number of events (and so requiring pagination) – but I do often find myself needing code like this as a quick starting point to help people out so will record it here for posterity and my own convenience :-)   [Read more...]

CleverWP Interview

It was fun to be interviewed by Lars Koudal of CleverWP.com recently. Check out the interview!

Screenshot showing a segment on an interview on cleverwp.com

Interview on CleverWP.com – a source of tips, tricks and WordPress related news.

Say Thanks to Your Customers

First things first: I’d like to stop and say thank you to all of my customers and everyone I work with – and wish you all a very happy christmas and new year. By happy coincidence, this post is all about saying thanks: saying thank you to your customers when they buy something is a nice touch and I’m going to look at doing just that using this tasty recipe:

  • WordPress 3.5 and Shopp 1.2.5 will form the base
  • Holly and berriesContent templates for the decorative icing
  • Post functions and a humble page, because if you are doing this for a client they may want to easily change the thank you message itself – especially at or following this time of year – without having to get their hands sticky mixing code and rolling out HTML

Once a customer places an order, a confirmation email is dispatched automatically, so half the work is already done here. All we want to do is shoehorn in a little message and what we will do is use a custom template to override the default one used by Shopp.

The process of overriding default templates is covered over in the Shopp documentation quite nicely, but for anyone who is unfamiliar with this the basic idea is to create a directory named shopp within your theme directory. For instance, if your theme is Twenty Eleven then the path to any custom Shopp templates would look like this:

wp-content/themes/twentyeleven/shopp/ ...

In this case, we are going to override email-order.php so at the end of this exercise a new version of that file should live in the above folder, like so (obviously if you are using a different theme then twentyeleven isn’t going to be part of the path):

wp-content/themes/twentyeleven/shopp/email-order.php

If you’ve already installed custom templates then you may even have this file already in place. If you do, it’s contents probably look something like this:

Content-type: text/html; charset=utf-8
From: <?php shopp('purchase','email-from'); ?>
To: <?php shopp('purchase','email-to'); ?>
Subject: <?php shopp('purchase','email-subject'); ?>

<html>

<div id="header">
<h1><?php bloginfo('name'); ?></h1>
<h2><?php _e('Order','Shopp'); ?> <?php shopp('purchase','id'); ?></h2>
</div>
<div id="body">

<?php shopp('purchase','receipt'); ?>

<?php if (shopp('purchase','notpaid') && shopp('checkout','get-offline-instructions')): ?>
    <p><?php shopp('checkout','offline-instructions'); ?></p>
<?php endif; ?>

</div>

</html>

So pretty straightforward. This is an approximation of what it’s output looks like:

Freshly Baked Oatcakes

Order 15987

Order Num:15987

Order Date: December 22, 2012 3:35 pm
Billed To: XXXXXXXXXXXX1111 (Visa)
Transaction: (Authorized)
Billed to
Tony Bignose
123 Hoola Avenue
Nimble Creek, British Columbia
Canada
Ship to
Tony Bignose
123 Hoola Avenue
Nimble Creek, British Columbia
Canada
Items Ordered Quantity Item Price Item Total
Premium Grade Oatcakes, 40kg 1 $41.32 $41.32
$41.32
Shipping $0.00
Tax 0%
Total $41.32

What I want to do here is insert a little message below the header but above the minutiae of the order itself. This can be accomplished very easily simply by inserting a snippet into the template, like so (noting the new paragraph inside div#body):

Content-type: text/html; charset=utf-8
From: <?php shopp('purchase','email-from'); ?>
To: <?php shopp('purchase','email-to'); ?>
Subject: <?php shopp('purchase','email-subject'); ?>

<html>

<div id="header">
<h1><?php bloginfo('name'); ?></h1>
<h2><?php _e('Order','Shopp'); ?> <?php shopp('purchase','id'); ?></h2>
</div>
<div id="body">

<p> We would like to thank you for placing an order with 
<em>Freshly Baked Oatcakes</em>
and wish you all the best for the festive season. </p>


<?php shopp('purchase','receipt'); ?>

<?php if (shopp('purchase','notpaid') && shopp('checkout','get-offline-instructions')): ?>
    <p><?php shopp('checkout','offline-instructions'); ?></p>
<?php endif; ?>

</div>

</html>

It’s that simple and if you are comfortable with everything described so far then that is probably all you need to know. Sometimes though it might be desirable to make the process of editing the message easier – such as if you are a designer/developer handing off the site to a client and want to empower them to make changes by themselves, without diving into a mix of HTML and PHP. [Read more...]

Considering Cache Exceptions

Across on his Optimize My Shopp blog, Lorenzo talks through the basics of setting up W3 Total Cache to work with Shopp. The most important part of this in my opinion is the setting up of exceptions and is just as applicable to other combinations of e-commerce and caching plugin as it is to Shopp/W3TC.

Since caching effectively “freezes” the content of a page we obviously want to avoid this on a cart or checkout page, where it could either cause a lot of confusion for customers or – worse still – the details of a previous customer might be frozen in place until the cache clears.

Lorenzo covers this nicely, but it’s also worth thinking about widgets, such as shopping cart or login widgets. We definitely don’t want to see these being “frozen” – certainly we don’t want everyone to see a single customer’s shopping cart.

Of course, if these widgets have been added across the site then they might not be covered by the basic exceptions set up in W3TC so it’s worth bearing that in mind and giving further consideration to the positioning and use of that sort of widget, or to other methods of preventing there being cached.

First Look at Habari

Once in a while it’s nice to get some fresh perspective and look at alternative ways of tackling similar problems. I’ve been mostly been operating in and around WordPress for quite a while now – but there are some other bits of software out there that can do similar things which I’ve always wanted to take the time to explore.

Habari is one of those and with the release of version 0.9 I thought I’d spend a little time looking at what on the surface looks like it might be a lighter, cleaner blogging platform than is WordPress[1].

Installation is straightforward, just as it is with WordPress, and features a single page installer. What I liked about this was that it also gave a choice of theme and basic plugins to enable right off the bat.

Screenshot showing plugin and theme options during installation of Habari

When you install Habari you can select a theme and plugins to get you up and running as quickly as possible.

I opted for the new (and responsive) Wazi theme, since I had just read about it in the 0.9 announcement, and went with the default selection of plugins. One click later and my new blog was installed and active, all as easy as pie. Next, I decided to login to the dashboard and was pleasantly surprised.

Screenshot showing the basic Habari dashboard

I also liked the menu, complete with keyboard shortcuts for rapid access.

If you’ve used WordPress then you’ll know that the dashboard is a busy, meta-box festooned place. By contrast, Habari’s dashboard contained nothing unnecessary – it felt very clean and ordered. [Read more...]

Glue for Virtual Pages

I’ve added virtual page support to Shopp SEO Glue – a plugin that helps WordPress SEO by Yoast to work with the Shopp ecommerce plugin. Although generally speaking I don’t really envision search engines pouring over the customer account page or the order confirm page, I’ve added a screen to allow control over this because:

  • I felt like it made things a little more complete
  • Some people might be doing interesting things with their store that puts these pages in the spotlight in a way that might not be true of more run-of-the-mill installations
Screenshot of the Shopp SEO Glue admin page, found in Shopp > Setup > SEO

Easily assign titles and descriptions to virtual pages like the cart or checkout.

Version 1.5 of the plugin can be downloaded from it’s home in the WordPress plugin directory.

Taxonomy Structures in WordPress

I was working on an importer for a custom (hierarchical) taxonomy in WordPress recently and was baffled by some strange behaviour. My CSV test data looked a little like this:

Paints & Finishes, Emulsions
Paints & Finishes, Varnish
Paints & Finishes, Barn Paint
Lettering, Small
Lettering, Large
Folding Canopies
Energy Products, Juice

Which, I hoped, would result in a tree-like structure as follows:

  • Paints & Finishes
    • Emulsions
    • Varnish
    • Barn Paint
  • Lettering
    • Small
    • Large
  • Folding Canopies
  • Energy Products
    • Juice

So 4 root items and 6 children. So I was a bit miffed to see that, although both the terms and term_taxonomy tables were correctly populated and the parent:child relationships properly established, only the root items were displaying (when I logged into the dashboard and visited the WordPress-generated taxonomy list, that is). I also noticed that:

  • If I ran the import a second time, although this quite correctly didn’t cause any further changes to the terms or terms_taxonomy tables, the missing child categories showed up
  • Or, if I didn’t run the import a second time – but deleted/trashed all of the root categories, then the child categories magically materialized out of thin air … although having been orphaned by the previous delete operation they were now promoted to being root items

Clearly I was missing something. The answer of course is that traversing a table with ID and Parent ID columns to establish the hierarchy on every single request would be a fairly intensive operation. Instead, WordPress caches this data – within the options table – and it was this cache which had not been refreshed. [Read more...]

Facebook Timezone Confusion

Lately there seems to have been a bit of confusion over importing events from Facebook into Modern Tribe’s The Events Calendar plugin for WordPress (when using the Facebook Events add-on)[1].

Update: future versions of Facebook Events ought to have a fix (for the problem I’m describing here) built right in, and Tim has posted an official fix you can use in the interim – see here.

This has been typified by people living outside of the PST timezone importing events only to find they are incorrectly offset by a number of hours. Today I wanted to look at how this can be controlled when setting up an event on Facebook as well as an alternative means of dealing with the problem.

Screengrab of the Facebook add event modal

Control over timezones for events can be exercised when setting up the event.

When you create a new event Facebook will intelligently determine what the timezone should be. Of course, it can only do this if it has a valid address to work with. If for some reason it can’t then you may be interested to know that you can clear the place automatically inserted in the Where field and enter a new location, giving you an opportunity to play around with different addresses until you create one that Facebook recognises.

By exercising control over the timezone in this way you should be able to import events into The Events Calendar without any timezone issues. If for some reason you are still hitting a wall then an alternative solution is available that offsets the imported time by a number of hours programmatically. [Read more...]