Why do so many websites offer 3 levels / plans for their SaaS product?

A random conversation I had this week at the Canadian Football League reminded me of a question I was asked and looked up the answer to nearly a decade ago: Why do so many websites offer three levels or plans to choose from when it comes time to purchase their product? And really, what is the optimal amount of choices to make available to a potential client?

The answer is multifaceted, but as you’d expect, the prevailing wisdom is that 3 choices – and elevating one as the best or most popular – works best. Here’s a quick rundown as to why.

The Centre Stage Effect. Formal psychological studies have been done on the positioning of products on a page, and it appears that consumers infer that the middle option is placed there because of its popularity (a self-fulfilling prophecy if there ever was one).

The Compromise Effect. UXmatters has a great paper on shortcut decision making; it mentions a research study that had one set of study participants be offered two microwaves at a $110 and $180 price point; participants chose fairly evenly, with a small majority preferring the cheaper option. But when a second set of study participants was offered three options, a clear winner emerged: The middle price point. The conclusion? When a consumer can’t decide whether to go high or low, a compromise option that sits in the middle is what our mostly logical minds push us towards.

Also: The Bandwagon Effect. Further, studies have illustrated that when consumers are pointed towards a choice and given the information that it is the most popular choice amongst their peers, the middle choice becomes even more compelling. Basically, consumers who may have little information at the time of their purchase as to what “level” fits them best will use whatever information is at hand – like the popularity of a choice – to finalize their decision.

Controlling the PHP error reporting level of WordPress

One of the annoyances of working with WordPress I’ve had for ages is trying to use the error_reporting() function to do something like temporarily turn off deprecation warnings; WordPress ignores these settings entirely.

Turns out the cause of this is the wp_debug_mode() function, which gets called by wp-settings.php and sets error_reporting(E_ALL). As a result, putting your own call to the error_reporting() function in wp-config.php won’t have any effect; you need to place your call after wp_debug_mode() runs. One smart solution is to create a very simple mu-plugin for this purpose.

Of course, it goes without saying that ignoring errors and warnings is done at your own risk.

Making scheduled posts in WordPress public so they can be scheduled / scraped on social media

The basics of scheduling in WordPress are quite simple: If you give a WordPress post a published date set in the future, it’ll remain “hidden” on your website until that date and time arrives. It’ll then appear, right on time, at the top of your list of public posts.

But here’s an interesting problem: Social media is now a major driver (maybe the driver) of traffic to digital media websites. When you schedule your post in WordPress, you’ll also naturally want to schedule that post to appear on Facebook and Twitter – but that would require the WordPress post to be public, which in its “future” post status isn’t yet.

// Allow site visitors to view posts in the "future" post status.
function show_future_posts( $posts ) {
   global $wp_query, $wpdb;

   if ( is_single() && $wp_query->post_count == 0 ) {
      $posts = $wpdb->get_results($wp_query->request);
   return $posts;
add_filter('the_posts', 'show_future_posts');


What makes a great product manager?

There’s a terrific post by Brandon Chu on Medium titled MVPM: Minimum Viable Product Manager that does a great job at explaining that while it’s impossible to have in-depth knowledge of the technical, business and user experience legs of the product tripod, knowing key elements in each area makes you so much more able to handle the decision making that you’ll be doing every minute of the day.

How to drop a MS SQL Server database

Dropping a database in MS SQL Server is not a one-step process; you’ll need to do the following:

USE master;
DROP DATABASE [databasename];


How are Canadians using their mobile devices? What kind of devices do they own?

The folks at Yahoo! / Flurry Analytics regularly post Flurry Insights, gleaned from data accumulated from their analytics SDK that’s embedded in hundreds of thousands of mobile devices. Back in 2015, they posted data they’d accumulated about mobile device usage in the Canadian market:


Some quick notes from the above:

  • 86% of time on a mobile device is spent in an app; only 14% of the time are users in a web browser
  • Social media apps (Facebook, Twitter and various others) gobble up 37% of time – roughly a third of the time someone is using their phone/tablet – all on their own
  • Playing music, reading an e-book or watching a video is how 9% of time is spent by Canadians on their mobile device

Of course, that’s not to say that you can’t create an app that takes up 50% of someone’s time on their devices, even over the long haul. But it’s clear that for the average Canadian, there’s only a small slice of time that they’re likely to spend using the new app you’ve created for them.

Next up: What kind of devices are in use out there? Flurry Analytics has a second post (from 2016 this time) with a global form factor breakdown:



How to get WordPress post permalinks directly from the MySQL database

While generating a CSV of old-and-new URLs for a site I’ve been busy migrating to WordPress, I ran into this brilliant bit of semi-working code to get the permalink for Posts using a pure SQL query (for MySQL):

SELECT wpp.post_title, wpp.guid, wpp.post_date,
       REPLACE( REPLACE( REPLACE( REPLACE( wpo.option_value, '%year%', DATE_FORMAT(wpp.post_date,'%Y') ), '%monthnum%', DATE_FORMAT(wpp.post_date, '%m') ), '%day%', DATE_FORMAT(wpp.post_date, '%d') ), '%postname%', wpp.post_name ) AS permalink
  FROM wp_posts wpp
  JOIN wp_options wpo
    ON wpo.option_name = 'permalink_structure'
 WHERE wpp.post_type = 'post'
   AND wpp.post_status = 'publish'
 ORDER BY wpp.post_date DESC;


How to disable WordPress’s internal search system (while still using its search page templates)

On websites like Sportsnet.ca ( http://www.sportsnet.ca/ ) and Maclean’s ( http://www.macleans.ca/ ), the amount of content on those WordPress websites long ago exceeded the level that the built-in WordPress search system can capably handle.

Third-party search solutions such as Google CSE (Custom Search Engine) are being utilized instead, but there is a momentary slowdown before the page loads that is caused by the WordPress site executing its own internal search query before it displays the search results page. In order to remove this delay (and the unnecessary database request), add the following code to the theme’s functions.php file:

// Disable WordPress's internal search query (as much as we can), letting Google CSE handle that.
function internal_search_disable( $query ) {
    if ( !is_admin() && $query->is_main_query() ) {
        if ( $query->is_search ) {
            // Add a filter that effectively returns no results ever.
            add_filter('posts_where', 'internal_search_filter_where');
    return $query;
add_action('pre_get_posts', 'internal_search_disable');
// The WHERE search filter for disabling the internal search system.
function internal_search_filter_where( $where = '' ) {
    $where = " AND 0 = 1";
    // Once added, remove the filter to stop affecting other queries on the page.
    remove_filter('posts_where', 'internal_search_filter_where');
    return $where;

How to add an AJAX function / URL to WordPress

Put this function in your theme’s functions.php (or even better, in an ajax.php file in the theme that is require’d in):

/* The function ajax_read_more() will be called when the following URL is requested
 * from WordPress:
 * http://www.yoursite.com/wp-admin/admin-ajax.php?action=read_more&limit=5
function ajax_read_more() {
    // Take in a few input parameters from $_GET or $_POST (depending on how you're passing the values) about
    // what data to retrieve and display.
    $num_limit = (int) $_POST['limit'];
    $str_category_name = filter_var($_POST['category_name'], FILTER_SANITIZE_STRING);
    // Call some built-in WordPress functions just to demonstrate that we can.
    $user_id = get_current_user_id();
    // Now let's return some JSON data to whatever called this URL (we can return HTML, XML or whatever else too,
    // just make sure to set the appropriate Content-Type header).
    $arr_sample_data = array('key' => 'value');
    echo json_encode($arr_sample_data);
    exit; // You must use exit to end an AJAX function in WordPress, or it'll append a 0 to the output.
add_action( 'wp_ajax_read_more', 'ajax_read_more' ); // This action exposes the AJAX action "read_more" to logged-in WordPress users.
add_action( 'wp_ajax_nopriv_read_more', 'ajax_read_more' ); // This action exposes the AJAX action "read_more" to anonymous (not logged in) WordPress users.

Then all you need to do is write a jQuery or even just a regular HTML form that submits a request to your new AJAX URL:

    type: 'get',
    url: '/wp-admin/admin-ajax.php?action=read_more&limit=5',
    dataType: 'json'
.done( function( response ) {

One last thing – visit your WordPress site’s Permalinks page in order to rebuild the list of URLs WordPress will respond to. This will make your new URL (for the AJAX function) active.

Forcing images to conform to a 16:9 aspect ratio

In the Bootstrap front-end framework, you can add the classes embed-responsive embed-responsive-16by9 or embed-responsive embed-responsive-4by3 on a DIV that wraps an IFRAME, EMBED or OBJECT element to ensure that video players always conform to a 16:9 aspect ratio on your responsive website (more info).

But what about images? In cases where content has been migrated onto a WordPress site, re-creating all thumbnails in the proper aspect ratio may just not be doable – or you may simply not have the controls in place to ensure that all images are of the correct aspect ratio. Luckily there appears to be a trick in CSS that can force aspect ratios. To get it to work, you’ll need to write CSS for a DIV that wraps the actual IMG tag like so:

.img-responsive-16by9 {
    display: block;
    height: 0;
    padding-bottom: 56.25%;
    overflow: hidden;

That’ll force the image output with the HTML illustrated below to respect a 16:9 aspect ratio:

<div class="img-responsive-16by9">
    <img src="http://www.570news.com/wp-content/blogs.dir/sites/3/2015/04/1429827607_NSD502266764_low.jpg">