Custom product types in drupal

For the Scodigo site we wanted to be able to sell licenses to use the SmartPill PHP Edition plugin. For the most part that just involves a standard ecommerce flow, and so we opted to use drupal’s suite of ecommerce modules, but we wanted to make some customizations along the way, such as being able to offer product variations (eg. different user counts for the licenses) and generating and emailing a license rather than shipping a product or delivering access to a file. To achieve that I created two custom modules: a product type called ‘licensable’ and a license management module creatively titled ‘licenses.’

I’m going to presume that anyone following along with this post is already familiar with building drupal modules. If you’re not I’d recommend starting with the module developer’s guide over on the drupal site. I’m also going to be writing about developing for drupal 4.7 with ecommerce 2.1.x as I’ve not had a chance to try this out in version 5.

Permissions

The first step I took was to define a set of three permissions for my new product type. To do that we use drupal’s hook_perm and simply return an array with the name of the permissions:

function licensable_perm() {
  return array('create licensable products', 'edit own licensable products', 'view licensable subproducts');
}

Now our module’s permissions will be available through the access control menu, and we can test for them with the user_access function. We use the hook_access callback to tell drupal when to apply our permissions:

function licensable_access($op, $node) {
  global $user;

  switch ($op) {
    case 'view':
      break;
    case 'create':
      return user_access('create licensable products');
      break;
    case 'update':
    case 'delete':
      return user_access('edit own licensable products') && ($user->uid == $node->uid);
      break;
  }
}

The Product API

Since this is a product we need to connect into the ecommerce module’s hook_productapi function. That works like most other drupal callbacks, sending in a reference to a node object, and the name of the operation to be performed. The key operations we’re interested in are:

wizard_select
Provides a list of product options provided by this module to be presented in the product creation wizard
subproduct_types
Provides a list of the types of sub products this product supports. In this case that allowed us to have a parent ‘licensable product’ which was the overall product, and then specific license types (single-user, 5-user, etc) as its children.
attributes
Products can have numerous attributes assigned to them. You can use this operation to do calculations about whether products are in stock, and related tasks.
cart add item
This is called whenever a user tries to add a product to the cart. Sub products need to intercept it to make sure the correct variation ends up in the cart, but we just let the standard subproducts module handle it.

As this is a license which can be generated on-demand our product is always in stock. And for most operations we simply pass the request up to the chain to the generic product module. Our function looked like:

function licensable_productapi(&$node, $op, $data = null, $a4 = null) {
  switch ($op) {
    case 'wizard_select':
      return array('licensable' => t('licensable product'));
      break;
    case 'subproduct_types':
      return array('licensable');
      break;
    case 'attributes':
      return array('in_stock');
      break;
    case 'cart add item':
      return subproducts_cart_set_subproduct_variation($node, $data);
      break;
    default:
      return module_invoke('generic', 'productapi', $node, $op, $data, $a4);
      break;
  }

A Final Touch

One other requirement we had was not to show the node pages for individual subproducts. Instead we’re showing a general product page and then a table of options. So as well as making sure the subproduct nodes weren’t directly linked to, we hooked into the node api to intercept requests for those nodes and redirect them to the parent product:

function licensable_nodeapi($node, $op, $arg) {
  // If the node has a pparent value it is a subproduct so we know we need
  // to redirect it.
  if ($op == 'view' && $node->ptype == 'licensable' &&
    $node->pparent && ! user_access('view licensable subproducts'))
  {
    if ($_SERVER['REQUEST_URI'] == url('node/' . $node->nid)) {
      $url = preg_replace('!^/!', '', url('node/' . $node->pparent));
      drupal_goto($url);
    }
  }
}

Conclusion

Creating new product types in drupal is a very simple process, and very flexible with it. This is admittedly a straightforward example, but also serves to show just how simple it can be. In a follow up post I’ll show how we hooked in to actually generate the licenses and send them out.

Tags: , , , , ,

Comments are closed.