a work on process

Viewing posts in category: Snippets

Custom product types in drupal

17 February 2007 (12:34 pm)

By James Stewart
Filed under: Snippets
Tagged: , , , , ,

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.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Syntax highlighting without breaking HTML

6 December 2006 (3:06 pm)

By James Stewart
Filed under: Snippets
Tagged: ,

Ruby on Rails provides the very nice helper method highlight to identify search terms within a string. We’ve been using it quite a bit on a large project but recently began to notice that it broke the HTML in certain places.

It turned out that our problem was that our search term was showing up within an attribute on a tag, and so highlight, oblivious to the content of the text it was parsing, was inserting tags in the middle of attributes, and all kinds of craziness was ensuing that just wouldn’t do.

A little time with google led me to Enhance Usability by Highlighting Search Terms, an article that appeared on alistapart a couple of years ago. In it Matt Riggott and Brian Suda explain how they used regular expressions in PHP to get around precisely this problem. I downloaded the code and set about converting it to ruby. Mine doesn’t do nearly as much as theirs, which includes tools to extract search terms from various search engines. Ours only needs to work with our internal search tool and so it was as simple as:

 def highlight_in_html(content, term)
   regexp = /(< [\/\!]*?[^<>]*?>)([^< ]*)/is
 
   # wrap the content in tags as the regexp only looks between HTML tags
   content = '&lt;p>' + content + '&lt;/p>'
 
   matches = content.scan(get_tags)
   content = matches.collect { |a,b| [a, highlight(b, term)] }.flatten.join
  end

It’s not thoroughly tested as yet, but seems to do the job.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Granting and Checking Permissions with LiveUser

15 February 2006 (10:52 pm)

By James Stewart
Filed under: Snippets
Tagged: , , , , ,

I was reminded by maxi_million in the comments on one of my previous LiveUser tutorial entries that I never completed the promised third entry in that series. After the initial procrastination wore off and I initially turned my mind to writing this piece, my main project using LiveUser ended up being converted (for various reasons) into a drupal site, so my further use of the library has been quite minimal. But I do have a little code sitting around, so will try and draw together a few notes on how I was using Liveuser.

While many powerful authorization systems are purely role-based, something that can be achieved in LiveUser with the groups functionality covered in the previous entry on this subject, LiveUser also supports much more fine grained permissions. Each permission is its own entity, and permissions are grouped into areas. I created my areas directly in the database so won’t be covering that here. Once you have your area’s id in the variable $area_id and an instance of LiveUser_Admin in $admin you can call:

$right = $admin->perm->addRight(
  array('area_id' => $area_id, 
    'right_define_name' => 'a name for your right'));

to create the right and capture its ID in $right.

You can then ‘grant’ that right to a user with:

$admin->perm->grantUserRight(
  array('right_id' => $right, 
    'perm_user_id' => $perm_user_id));

Or grant it to a group with:

$admin->perm->grantGroupRight(
  array('right_id' => $right, 
    'group_id' => $group_id));

Where I ran into problems was when I attempted to check those permissions. From the API documentation, it wasn’t clear how to check for a right given only its name, but after a little exploration I put together the following method (used within my wrapper class) that handles it. $this->user is an instance of LiveUser and $this->admin is an instance of LiveUser_Admin:

function checkRight($area_id, $right_desc) {
  if (empty($this->admin)) {
    $this->getAdminInstance();
  }
  $filter = array(
    "fields" => array("right_id"),
    "filters" => array(
      "area_id" => $area_id,
      "right_define_name" => $right_desc));
  $rights = array_keys($this->admin->getRights($filter));
  $right_id = $rights[0];
  return $this->user->_perm->checkRight($right_id);
}

You’ll note that the final line makes use of the _perm property. In PEAR coding conventions a preceding underscore means a method or property is private and should not be accessed from outside the class, but this was the neatest approach I was able to find in the time. If anyone can tell me how to do this within the approved public API I’ll happily update this entry.

Update: Be sure to check out the comments, where Lukas has been adding some very useful information.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Getting Started with LiveUser Permissions

4 August 2005 (12:26 pm)

By James Stewart
Filed under: Snippets
Tagged: , , ,

UPDATE (Aug 9th ‘05): Thanks to feedback from Lukas Smith and Laurens Nienhaus I’ve made some updates which show better ways to get at some properties.


Having described in my ‘Configuring LiveUser’ entry how to configure and instantiate LiveUser it’s now time to talk about how we start connecting together our login system with more sophisticated permissions management.

This time around we’re going to be making use of the LiveUser_Admin module, which can be instantiated using the same configuration array as LiveUser, with:

$admin = LiveUser_Admin::factory($liveuser_config);

LiveUser keeps ‘authentication’ and ‘permissions’ distinct and in order to get started with permissions we must create entries for our users in the permissions system.

The LiveUser Admin API provides us with the method:

$admin->perm->addUser($user_to_add);

to add a user to the permissions system, where $user_to_add is an array with keys matching the fields of the liveusers_perm_users table. In particular we need ‘auth_container_name’ and ‘auth_user_id’. Unfortunately there is no method which will generate this array for the currently logged in user, but we can get at the instance variables directly to perform:

$user_to_add = array();
$user_to_add['auth_container_name'] = $live_user->getProperty('containerName');
$user_to_add['auth_user_id'] = $live_user->getProperty('auth_user_id');

$admin->perm->addUser($user_to_add);

Once we have a user we can start to create permissions and grant them those permissions, but before moving on to that I wanted to set up permission groups and add users to those groups (we can also grant permissions to groups and in general this is the facility I’m likely to use most).

To add a group we make use of the addGroup() method:

$admin->perm->addGroup($group_info);

Where $group_info is an array with the keys ‘group_define_name’ (string) and ‘is_active’ (boolean). I used it as:

$group = $admin->perm->addGroup(array(
'group_define_name' => 'admins',
'is_active' => 1));

Having set up the group we’ll want to add users to that group, which is achieved through the method:

$admin->perm->addUserToGroup($data);

Once again the parameter is an associative array with the keys ‘group_id’ and ‘perm_user_id’. The return value from addGroup will have given us the group ID, or we can retrieve it using the getGroups method:

$params = array(
'fields' => array('group_id'),
'filters' => array('group_define_name' => 'admin'));
$group_details = $admin->perm->getGroups($params);

which will return something like:

Array
(
[0] => Array
(
[group_id] => 2
)

)

Meaning we can add our user to the group using:

$group_info['group_id'] = $group_details[0]['group_id'];
$group_info['perm_user_id'] = $live_user->getProperty('permUserId');;
$admin->perm->addUserToGroup($group_info);

So there we have it, our ‘admins’ group is defined and our current user is a member of it. Next time I’ll cover how we use this to create, edit, grant, revoke and check rights.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Configuring LiveUser

29 July 2005 (3:00 pm)

By James Stewart
Filed under: Snippets
Tagged: , , ,

For a current (PHP) project I need a highly flexible authentication and permissions management system. Having heard plenty of mentions on the various PEAR lists I decided to try out LiveUser, which seems to currently be the most comprehensive such system for PHP. It’s a large codebase that seems to cover most of the necessary permutations and includes a number of prominent coders amongst its developers.

What I hadn’t taken into account, of course, was the lack of documentation. I spent quite a while trawling through their wiki and googling before concluding that the best way to work LiveUser out would be to work through the code piece by piece. And since that’s a long, slow process it seemed worth documenting here. I’m going to do that step by step, starting today with configuration and instantiation.

LiveUser’s initial configuration is managed through a large associative array that should be passed to the factory in order to instantiate the core object. Unlike some of the other arrays used as arguments to functions, this one is documented inline, but it still took a while to pick apart.

Here’s an example:

$liveuser_config = array(

First up we have some core parameters for LiveUser

autoInit determines whether or not the class tries to instantiate the various ‘containers’ when it is first called

'autoInit' => true,

the login parameter allows us to specify whether login should be attempted off-the-bat and whether or not the session ID should be regenerated after a successful login.

'login' => array(
'force' => true,
'regenid' => true),

With the core parameters set, we can move onto the ‘containers’. LiveUser employs containers to allow for flexibility. Containers can be thought of as ‘plugins’ that allow access to any sort of authentication or permissions management system you want to connect.

First up I define my permissions container. There are three container types that come with LiveUser out of the box: ’simple’, ‘medium’, and ‘complex’. I’m looking for as much flexibility as possible so am using ‘Complex’.

We then provide some ’storage’ parameters. LiveUser provides several storage option for permissions (primarily PEAR::DB, MDB and MDB2) and you can add extras in. Here I am using PEAR::DB and so specify a DSN and the prefix I’m using for my tables.

If I already had a DB object instantiated, I could remove the ‘dsn’ parameter and replace it with ‘connection’ which would be a reference to my existing connection.

'permContainer' => array(
'type' => 'Complex',
'storage' => array(
'DB' => array(
'dsn' => $dsn,
'prefix' => 'liveuser_'))),

For me, one of the key draws of LiveUser for me was the fact that I could authenticate against multiple sources for one application. This application includes a directory for a membership-based organisation, and I want all members to be able to login. There are also a few administrators who are not members, but need to be able to log in to the site.

authContainers is itself an associative array, with the keys being the names by which you want to address the different authentication sources. In this case ‘members’ and ‘admins’. You can specify a few options that should be familiar to anyone who’s used PHP’s sessions and then a few others. I’m again using the PEAR::DB interface for my authentication so I specify type as ‘DB’.

Options for the DB container are then given in the ’storage’ parameter. As with the permissions, ‘dsn’ can be replaced with ‘connection’. ‘alias’ is an array that maps the table (’users’) and fields that LiveUser’s DB container uses by default to the table and fields that we have in our application.

'authContainers' => array(
'members' => array(
'type'            => 'DB',
'loginTimeout'    => 0,
'expireTime'      => 3600,
'idleTime'        => 1800,
'updateLastLogin' => false,
'allowDuplicateHandles' => false,
'allowEmptyPasswords'   => false,
'passwordEncryptionMode' => 'PLAIN',
'storage' => array(
'dsn' => $dsn,
'prefix' => '',
'alias' => array(
'users' => 'member',
'auth_user_id' => 'id',
'is_active' => 'status',
'handle' => 'username',
'passwd' => 'password'))),
'admins' => array(
'type'            => 'DB',
'loginTimeout'    => 0,
'expireTime'      => 3600,
'idleTime'        => 1800,
'updateLastLogin' => false,
'allowDuplicateHandles' => false,
'allowEmptyPasswords'   => false,
'passwordEncryptionMode' => 'MD5',
'storage' => array(
'dsn' => $dsn,
'prefix' => '',
'alias' => array(
'users' => 'admins',
'auth_user_id' => 'id',
'is_active' => 'id',
'handle' => 'username',
'passwd' => 'password')))));

With that array set up, LiveUser can easily be instantiated using:

$live_user = LiveUser::factory($liveuser_config, $username, $password);

or

$live_user = LiveUser::singleton($liveuser_config, $username, $password);

and we can check whether the user is now logged in with

$live_user->isLoggedIn() which returns true if login succeeded, false if it failed.

(NB: LiveUser’s own database schema is distributed within the examples that come in the PEAR package. It is provided as an MDB-compliant XML file)

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 
« Previous PageNext Page »