How To Build A Plugin And Extend The BC4WP Experience

Topher DeRosia
BigCommerce Developer Blog
12 min readJan 2, 2020

One of the keys to the success of WordPress is its extensibility. The ability to dramatically change the feature set of the platform without editing the platform itself is tremendously powerful. It’s what allows the BigCommerce for WordPress plugin to exist.

Quality WordPress plugins feature the same extensibility, and the BigCommece for WordPress plugin is no exception. In this article, I’ll demonstrate an example of a plugin that creates new functionality around the BigCommerce for WordPress plugin, and then I’ll link to several other examples.

Introducing the Plugin

We’re going to make a plugin that creates a Featured Product, similar to the way WordPress has a Featured Image. You’ll be able to associate a Product with a Post or Page and then edit the theme to render it where it’s contextually appropriate.

Our purpose in making this plugin is to allow the theme creator to render products in a completely new way, allowing you to market your products in ways that the original plugin developers could never have imagined.

You may download the plugin from GitHub right now and see it working. Simply download the zip file and install it in WordPress using the Upload Plugin feature on the WordPress New Plugin page.

File Structure

WordPress plugins typically have a folder name that uses hyphens instead of spaces or underscores. Our plugin is in a folder called bc-featured-product. Then, within that folder is a main controller file, traditionally named the same as the folder, but with .php at the end, so we have a file called bc-featured-product.php.

With a more complicated plugin the main file is usually a controller of sorts, loading other files, initializing objects, etc. Our plugin does only one thing, so all of our code is right in the main file.

Code Structure

Our functional code is in a single class called BC_Featured_Product. WordPress coding standards dictate that function and class names use underscores in place of spaces or camelcase.

Immediately after the class is an add_action function which instantiates an object of our class on the action hook plugins_loaded. This creates an object from our class in the WordPress load at the point where plugins are loaded. Then its output is available to WordPress from there forward.

Comments

The plugin has the usual WordPress comment block at the top (which we’ll go over in a few moments), and then uses PHP Docblock to comment the rest of the code.

Reviewing The Code

Header Comments

As mentioned above, the first thing in the file is the WordPress comment block. Here it is:

/**
* Plugin Name: Featured BigCommerce Product
* Plugin URI: http://bigcommerce.com
* Description: Provides a mechanism for associating a BigCommerce product with a Page or Post
* Author: Topher
* Version: 1.0
* Author URI: http://topher1kenobe.com
* Text Domain: wp-featured-bc-product
*/

This information is used both on WordPress.org (if you’ve submitted your plugin there) as well as inside WordPress itself. When you load the Plugins page, and see the plugin name and description, this comment block is where that information is coming from.

The next comment block is for the entire file. It explains what the file is for, when it was added, and the author.

/**
* Provides a mechanism for associating a BigCommerce Product with a Page or Post
*
* @package BC_Featured_Product
* @since BC_Featured_Product 1.0
* @author Topher
*/

Beginning the Class

Our plugin uses object oriented programming, so we have a comment block to introduce it, and then we start the class.

/**
* Main BC Featured Products Class
*
* Contains the main functions for the admin side of BC Featured Product
*
* @class BC_Featured_Product
* @version 1.0.0
* @since 1.0
* @package BC_Featured_Product
* @author Topher
*/
class BC_Featured_Product {

Instantiation, Properties, and Methods

The first three things in the class are a property declaration, the constructor, and then an instantiation method. These three things work in conjunction to facilitate creating an object, so we’ll discuss all three in quick succession.

The first thing we find is a private, static instance handle. Private means it can only be accessed from inside the class, and static means it can be accessed directly by other methods. It’s called instance because it holds an instance of the class (an object) without the convention of declaring an object outside the class. We assign it to null at the beginning here, and will assign it data later in the class.

/**
* Instance handle
*
* @static
* @since 1.2
* @var string
*/
private static $__instance = null;

Next is the constructor. Every class needs a constructor, but we’re not going to put anything in ours. It’s quite common to put things like add_action lines in the constructor, but that makes the class difficult to test with automated testing.

/**
* BC_Featured_Product Constructor, actually contains nothing
*
* @access public
* @return void
*/
private function __construct() {}

Instantiating an object in WordPress can be done in a variety of ways. Since we only want to ever instantiate this one once we are making this instance method. We’ll call it with an add_action, which you’ll see at the end of this post.

This method both creates the object and calls the setup() method. We’ll look at the setup method in a moment.

/**
* Instance initiator, runs setup etc.
*
* @access public
* @return self
*/
public static function instance() {
if ( ! is_a( self::$__instance, __CLASS__ ) ) {
self::$__instance = new self;
self::$__instance->setup();
}
return self::$__instance;
}

Our setup() method runs things that would normally be in the constructor. For our plugin we’re running two self methods. The first is the save method, for when the user chooses a related product and clicks the post Publish or Update button.

The second runs the self method that creates the meta box on the proper page that holds the UI for choosing a related product.

Pay close attention to how you refer to a method in a class when using add_action. Normally you simply give a function name, but inside a class you need to send an array that contains both the class reference ($this in this case) and the method name.

/**
* Runs things that would normally be in __construct
*
* @access private
* @return void
*/
private function setup() {
// only do this in the admin area
if ( is_admin() ) {
add_action( ‘save_post’, array( $this, ‘save’ ) );
add_action( ‘add_meta_boxes’, array( $this, ‘bc_products_meta_box’ ) );
}
}

This next method is long, so we’ll go over it in pieces. The general work that happens in this method is this:

  • Check to see if there’s already a value set for our featured product
  • Go get all the possible products
  • Create a meta box to hold our UI
  • Build a drop down list of products, optionally defaulting to a preset value

Let’s look at the details.

/**
* Render select box of BC Products
*
* @access public
* @return void
*/
public function render_bc_products_meta_box_contents() {

This code is running inside the admin area, and the first thing we want to do is get the data for the Post we’re working on, so we globalize $post.

global $post;

Then we set a nonce. Nonce stands for Number Used Once, and it helps make sure that a form submission came from the proper source. For more information about how nonces work check out the WordPress Developer Handbook section on nonces.

// Add a nonce field so we can check for it later.
wp_nonce_field( ‘wp-featured-products’, ‘wp_featured_products_nonce’ );

Next we need to check to see if this post already has an associated Product. The field name for the stored data is _bc_featured_product, so we use get_post_meta to get whatever is in that field. The function requires a post_id, so that it knows what post we’re talking about, and the last value is set to true because there’s only one instance of this field per post.

// go get the meta field
$bcp_meta_value = get_post_meta( $post->ID, ‘_bc_featured_product’, true );

Next we’re going to display some instructional text on the page. Note that the content that is not HTML is rendered using esc_html_e(). This does three things. The first is that it escapes any code that might nefariously be in the text, making it more difficult for attackers to slip problem code in. The second is that it makes the string available to translators. The second input in the function below is wp-featured-products, which is a unique identifier for this plugin, and allows translators to change that string for different languages.

The third thing that esc_html_e() does it echo the output, so you don’t need the echo command.

// Display the form, using the current value.
echo ‘<p>’;
esc_html_e( ‘Please choose from the existing products below. If you need to create a new Product, please go to ‘, ‘wp-featured-products’ );
echo ‘<a href=”’ . esc_url( ‘https://login.bigcommerce.com/' ) . ‘“>’;
esc_html_e( ‘The BigCommerce Admin ‘, ‘wp-featured-products’ );
echo ‘</a>.’;
echo ‘</p>’;

In this next section we’re going to query the database for all products. In the arguments array we’re saying that we want only BigCommerce products, we want only published products, we want all available (that’s the -1), we want to order by title, ascending.

Then we send the query with get_posts().

$args = array (
‘post_type’ => ‘bigcommerce_product’,
‘post_status’ => ‘publish’,
‘posts_per_page’ => -1,
‘orderby’ => ‘title’,
‘order’ => ‘ASC’,);
// The Query
$products = get_posts( $args );

Once we’ve run the query, we check to make sure we got a result back, and then loop through them to build a drop-down list.

Earlier we mentioned esc_html_e() as helping with translation and ALSO echoing. In this block we want to make strings available for translation, but they’re already being echoed, so we use a function that looks like this: __().

Additionally, where we’re outputting the title, we need to escape, but not translate, so we use esc_html().

// make sure we have results
if ( count( $products ) > 0 ) {
echo ‘<select name=”_bc_featured_product”>’ . “\n”;
echo ‘<option value=””>’ . __( ‘Please choose’, ‘wp-featured-products’ ) . ‘</option>’ . “\n”;
foreach ( $products as $key => $product ) {
echo ‘<option value=”’ . absint( $product->ID ) . ‘“‘ . selected(
$bcp_meta_value, $product->ID, false ) . ‘>’ . esc_html( $product->post_title ) . ‘</option>’ . “\n”;
}
echo ‘</select>’ . “\n”;
} else {
echo '<p>';
esc_html_e( ‘No products found, ‘, ‘wp-featured-products’ );
echo '</p>';
}
}

Now we’ve presented the user with a way to choose a related product, but we haven’t made a save() method yet. That comes now. The method takes a post_id, and the add_action way up above in setup makes sure that variable is passed properly.

/**
* Updates the options table with the form data
*
* @access public
* @param int $post_id
* @return void
*/
public function save( $post_id ) {

The first thing we do before saving data to the database is to make sure we have the proper permissions.

Our first bit of code here does two things. The first is to figure out what kind of post type we’re looking at with get_post_type_object().

The second thing is to see if the current user has edit privileges for this post type.

If it fails either test the function returns nothing without saving.

// Check if the current user is authorised to do this action.

$post_type = get_post_type_object( get_post( $post_id )->post_type );if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) {
return;
}

Now that we’ve determined that this user is allowed to edit this post type, let’s make sure the update request is coming from a valid source.

In the method above we set a nonce, now that the form has been submitted, we can look for that nonce and verify that it came from the right place. If it fails the test then the function returns nothing without saving.

// Check if the user intended to change this value.

if ( ! isset( $_POST[‘wp_featured_products_nonce’] ) || ! wp_verify_nonce( $_POST[‘wp_featured_products_nonce’], ‘wp-featured-products’ ) ) {
return;
}

WordPress has a great feature called “auto saving” where it will save your open document every few seconds, so that if your browser crashes or you lose your internet connection or something you can come back and your “unsaved” work will still be available.

You do NOT want our custom save method here to run every time there’s an auto save. That should be reserved for page content, not our setting, so here we test to see if we’re auto saving or not.

If this is an auto save then we return the post_id, but do not save.

// If this is an autosave, our form has not been submitted, so we don’t want to do anything.if ( defined( ‘DOING_AUTOSAVE’ ) && DOING_AUTOSAVE ) {
return $post_id;
}

The last thing we do, once we’ve passed all the tests above, is save the data to the database. Our data should always be a single integer, so we want to test for that right before save. In PHP, something that looks like a number could actually be an integer or a numeral, and either are acceptable, so we use the PHP function is_numeric to test.

If it’s numeric then we run the function that stores post meta data, update_post_meta(). It accepts the post_id, so it knows what post to attach the meta data to, the name of the value we want to save, and then the value itself.

Note that we’re doing one last data type enforcement here with absint(). An important security philosophy is that you should always validate your data as the very last thing before saving it. For more insight, check out this talk I gave in 2014 I spoke at WordCamp Orlando on the topic of data verification. absint() requires that the value either be an integer or it will reset it to 0.

// check to see if our input is numeric, so our absint doesn’t save a 0
if ( is_numeric( $_POST[‘_bc_featured_product’] ) ) {
// Update or create the key/value
update_post_meta( $post_id, ‘_bc_featured_product’, absint( $_POST[‘_bc_featured_product’] ) );
}
}
// end class
}

Now that we have our class complete, we want to instantiate an object at the right time. We could simply create a variable and make a new Object, but that would put it into the Global namespace, and we don’t need or want that. This is why we created the instance() method inside the class.

Now we can use add_action to create the object each time the plugins_loaded hook runs.

/**
* Instantiate the BC_Featured_Product instance
* @since BC_Featured_Product 1.0
*/
add_action( ‘plugins_loaded’, array( ‘BC_Featured_Product’, ‘instance’ ) );

What next?

This plugin facilitates associating a Product with a Post or Page, but it doesn’t actually DO anything with that association. This is where a theme or site developer would need to use a little bit of code.

In the theme template files the developer would enter:

$featured = get_post_meta( get_the_ID(), ‘_bc_featured_product’, true ); if (
! empty( $featured )
&&
is_numeric( $featured )
&&
function_exists( ‘bigcommerce’ )
) {
echo do_shortcode( ‘[bigcommerce_product post_id=’ . absint( $featured ) . ‘]’ );
}

The first line of the above code gets the associated featured Product. Then before rendering it we need to make sure of three things:

  1. we got something back
  2. the thing we got back is a valid number
  3. the BigCommerce for WordPress plugin is actually installed with function_exists()

If all three are true then we echo the output of do_shortcode() which calls the bigcommerce_product shortcode with a post_id. Also note we’re still running absint() on the value we’re sending to the database. ALWAYS sanitize input as the very last thing you do.

Examples

The flexibility of WordPress (and headless ecommerce in general) is one of the big advantages over a typical SaaS. If you need something that the SaaS doesn’t provide you can either write it yourself or download a plugin that does what you want.

Here are a few other examples of great addons of the BigCommerce for WordPress plugin.

Turn On Reviews For All

By default only logged in users can leave comments on Products in a BC4WP site. This plugin makes it so that anyone can leave comments, whether they’re logged in or not.

Hide WordPress Admin Bar For Customers

In WordPress, logged in customers are ALSO logged in WordPress users, which by default adds a black Admin bar at the top of the site. This is often undesirable, and this plugin simply removes it.

Create Interface For Changing Image Dimensions

The BigCommerce for WordPress plugin takes product images and creates 4 different sizes, and crops them all square. This plugin creates a user interface in the WordPress admin to all you to change the dimensions and turn crop on or off.

Create Product Feature Widget

This plugin creates a WordPress Widget that can be placed into any sidebar or widget area in WordPress. It provides options for showing various elements of a product, like image, titles, description, and a More button.

--

--

Topher DeRosia
BigCommerce Developer Blog

Topher is a Senior WordPress Strategist at Camber Creative.