a work on process

Viewing posts in category: Snippets

Generating an accessible tag cloud with drupal

23 November 2007 (12:32 pm)

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

A couple of months ago I found myself needing to generate a “tag cloud” in drupal. The contents of the cloud would be all the children categories of a container and as is the norm with tag clouds it needed to display those children at varying sizes based on how popular they are. I looked around at the existing options but none fitted my requirements in how they integrated with the category module, or producing semantically helpful html.

So I rolled my own. It’s a quick module that just provides a few simple utilities. The cloud is initially generated as an unordered list, with the number of nodes matching each tag included in brackets, eg:

<ul>
  <li><a href="/node/1" rel="tag">tag one (5 entries)</a></li>
  <li><a href="/node/3" rel="tag">tag three (8 entries)</a></li>
  <li><a href="/node/2" rel="tag">tag two (2 entries)</a></li>
  <li><a href="/node/6" rel="tag">tag six (4 entries)</a></li>
</ul>

There’s then a jquery function, called on document load, that works through the list, hides the content in the brackets, and changes the size of the text based on the tag’s popularity.

You can include it in a page by calling:

< ?php tagcloud_include($container_id); ?>

within your theme. I’ve tested the module against drupal 4.7 but it should work fine in 5.x (and I guess 6.x).

I don’t have time or the inclination to do much more work with this module, or to provide support for it, but it seemed worth throwing out there in case anyone else wants to build on it. If you’d like to adopt it, let me know and we can collaborate.

If you’re interested, you can get the tarball here.

Recommend this post:

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

 

Since I wrote my first piece on extending a rails app to accept OpenID quite a few other tutorials and an official plugin have appeared to make that process easier. OpenID is quickly becoming quite mainstream, at least amongst developers, and that is very good news.

It’s becoming so mainstream in fact, that recently I’ve been asked to implement an OpenID server on top of an existing user database so that those users can have an easy single-sign-on option across a range of sites. Writing the server side piece is not quite so straightforward and there’s not much documentation yet. A few sample servers are available but the rails examples don’t run cleanly on the latest gems, so while I took some code from them it made most sense to start from scratch. Over the past couple of days I’ve hacked together something that works for me and even though it could still do with some polish a few notes follow. Please do use the comments to correct anything I may have gotten wrong or skipped over.

Firstly, a word on the process. The OpenID process works along the lines of:

  1. The client sends a request to the server to establish communication, shared keys, etc.
  2. Having established the keys, the client then redirects the end-user to the server to log in
  3. The server logs the end-user in however it wishes
  4. The server redirects the end-user back to the client with a query string indicating success or failure
  5. The client then confirms the success of the request with the server

I was starting from a very simple rails app that provided a Users resource and a Session resource. It’s really little more than a skeleton app with restful_authentication installed and configured. Users can log in and anyone can see a user’s page, and that’s all there is to it. I also had the ruby-openid gem installed and used a fair bit of code from the sample apps supplied with the gem.

Preparing The Existing Site

The first thing you’ll want to do is make sure that the HTML pages for your user resources have the correct headers. Chances are you will want users to be able to use their page (eg. http://yourapp.com/users/jystewart) as their OpenID but that won’t actually be where the login takes place, so you’ll want to put something like:

<link rel="openid.server" href="<%= session_url %>" />
</link><link rel="openid2.provider" href="<%= session_url %>" />
</link>

in the header block of your user page HTML. From that the OpenID client will know to redirect users to your sessions_url for authentication.

Preparing The Models

To my database that so far held the user details, I added four tables:

create_table :nonces do |t|
  t.string :nonce
  t.integer :created
  t.timestamps 
end
 
create_table :associations do |t|
  t.binary :server_url, :secret
  t.string :handle, :assoc_type
  t.integer :issued, :lifetime
  t.timestamps 
end
 
create_table :settings do |t|
  t.string :setting
  t.binary :value
  t.timestamps 
end
 
create_table :trusts do |t|
  t.integer :user_id
  t.string :trust_root
  t.timestamps 
end

The first three hold the various details we need for the negotiations with the client, and the fourth stores the details of any sites a given user has said they want to always trust so that we don’t need to confirm those details every time.

We also need an implementation of the OpenID::Store class for the OpenID library we’re using. I simply copied the one from the sample rails app distributed with the gem. With the models and that interface in place we can move on to managing the requests.

Distinguishing OpenID Requests

I decided to use a Mime::Type alias to distinguish between requests made directly in my app and those coming through OpenID. To do that I added:

Mime::Type.register_alias "text/html", :openid

in RAILS_ROOT/config/initializers/mime_types.rb and added the following code in my controllers:

before_filter :check_for_openid_request
 
private
  def openid_server # :nodoc:
    @openid_server ||= OpenID::Server::Server.new(ActiveRecordOpenIDStore.new)
  end
 
  def openid_request
    @openid_request ||= openid_server.decode_request(
      openid_params.delete_if{ |key, val|
        key == 'openid.session_type' && val == ''})
  end
 
  def check_for_openid_request
    @openid_params = session[:openid_params] if session[:openid_params]
 
    if openid_request.is_a?(OpenID::Server::CheckIDRequest)
      session[:openid_params] = @openid_params
      request.format = :openid 
    end
  end

Because there are going to be several steps to our process during which we want to be able to hang on to the OpenID request details, I chose to store them in the session

Handling The Requests

Having defined our Mime::Type alias, we can now use openid as a type in respond_to blocks. The first step then is to change our SessionsController#create action from:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    flash[:notice] = "Logged in successfully"
    redirect_back_or_default(user_path(current_user))
  else
    render :action => 'new'
  end
end

to:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    respond_to do |wants|
      wants.html { 
        flash[:notice] = "Logged in successfully"
        redirect_back_or_default(user_path(current_user))
      }
      wants.openid { 
        if @trust = current_user.trusts.find_by_trust_root(openid_params['openid.trust_root'])
          successful_openid_login 
        else
          redirect_to new_trust_url
        end
      }
    end
  else
    render :action => 'new'
  end
end

This checks for a successful login and if it’s an OpenID request then checks to see if we already trust that site. If we do, we call the ’successful_openid_login’ method and if not, we redirect the user to ask if they want to trust the client site. The successful_openid_login method looks like:

def successful_openid_login
  session[:openid_params] = nil
  @resp = openid_request.answer(true)
  @resp.add_field(nil, 'is_valid', 'true')
  add_sreg
  render_openid_response
end
 
def render_openid_response
  response.headers['Content-Type'] = 'charset=utf-8'
  @resp = openid_server.encode_response(@resp)
 
  case @resp.code
    when OpenID::Server::HTTP_OK
      render :text => @resp.body, :status => 200
    when OpenID::Server::HTTP_REDIRECT
      redirect_to @resp.redirect_url
    else
      render :text => @resp.body, :status => 400
  end   
end

And is almost entirely taken from the code supplied with the gem. I ran into some trouble with the ‘is_valid’ field not being added to the responses but being needed by my clients, so added the line

@resp.add_field(nil, 'is_valid', 'true')

The TrustsController implementation is quite straightforward and I’ll not go into details here, but will include it in the zip file you can download at the end of this entry.

This code will handle most of the process, but we have one remaining query to handle, and that is the final check_authentication request that is used to confirm everything fell into place. The actual request will come as a POST to your /session path so you will want to be ready to handle it there. I decided to implement a before_filter to intercept it and handle it separately rather than clutter my create method still further:

before_filter :handle_openid_check_auth_request
 
def handle_openid_check_auth_request
  if openid_params['openid.mode'] == "check_authentication"
    signatory = OpenID::Server::Signatory.new(ActiveRecordOpenIDStore.new)
    @resp = openid_request.answer(signatory)
    @resp.add_field(nil, 'is_valid', 'true')
    render_openid_response
  end
end

Once I had the code working acceptably I pulled it out into a separate module in the ‘lib’ folder. As the code matures it would probably be a good candidate for a rails plugin, but I’m not going to have time to put that together on my own for a while. If anyone wants to collaborate on that, get in touch in the comments (or by email) and maybe we can make it happen.

All the code above along with various other utility methods can be found in this zip file.

Recommend this post:

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

 

Protecting static files when deploying with Capistrano

3 October 2007 (4:59 pm)

By James Stewart
Filed under: Snippets
Tagged: , ,

There’s one project I work on where the client wants to be able to edit HTML files in the root folder, but I want to be able to deploy with capistrano. It’s a pain to have to log into the server and check whether those files have changed before each deployment so last night, I added the following to my deploy.rb:

desc "Make sure we don't overwrite manual static file changes"
task :before_deploy do
  captured = false
 
  run "svn status #{deploy_to}/current/public" do |ch, stream, data|
    if stream == :out and data.chomp.match(/^M/)
      captured = true 
    end
  end
 
  if captured
    run "svn commit #{deploy_to}/current/public -m 'Storing manual changes to static HTML before deploy'"
  end
end

It’s not the most elegant, but it’ll make sure that any files the client has changed (which were already in the repository) are committed, and it makes the deployment process just that little bit easier.

(thanks to Jonathan Weiss for a recent blog entry that reminded me to implement this.)

Recommend this post:

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

 

Extending drupal’s checkout process

18 February 2007 (9:33 am)

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

In yesterday’s post on creating custom product types in drupal I promised a follow up on how we were adding the license generation to our checkout. I’m not going to go through all the steps in great detail as most of what I wrote is specific to our situation, and the custom PHP module (written in C) that generates our licenses, but it took me a while to work out how to hook in, so here are a few words.

In addition to our custom product type module ‘licensable’, I also wrote a module called ‘licenses’ which allows the site administrator to specify types of licenses to go with specific products and generate licenses, and for users to review the licenses they’ve bought through their account. It’s in that module that I connected with the transaction API to generate licenses after a purchase.

The callback in question is ‘hook_ec_transactionapi’ and we needed to intercept the ‘update’ operation:

function licenses_ec_transactionapi($txn, $op, $a3, $a4) {
    switch ($op) {
        case 'update':
            if ($txn->payment_status == 2) {
                licenses_generate_license($txn);
            }
            break;
    }
    return true;
}

A payment_status of 2 means that the payment has been completed and we can generate the license. The ‘licenses_generate_license’ function can then loop through the transaction’s products and generate licenses for any that are licensable. Some rough code for that would be:

function licenses_generate_license(&$transaction) {
  $user = user_load(array('uid' => $transaction->uid));
 
  foreach($transaction->items as &$product) {
    if ($product->ptype == 'licensable') {
      // generate license
    }
  }
 
  // send email to user with their license details
}

Recommend this post:

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

 

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]

 
« Previous PageNext Page »