concepts

Conveniently Programming RSS Feeds in Drupal

Drupal feeds most of the content out of the box. If it doesn't do what you want, it allows you to use format_rss_item() and format_rss_channel(). This stuff is simple to use, but not very convenient by itself. There are two things that I like to do when building feeds:

  • Use theme functions to format items. (Makes designer happy.)
  • Make it as convenient as possible to program new types of RSS items (hello, PHP Metaprogramming).

I should mention that I'm working with Drupal 5.7, but any 5.x -6.x shouldn't be much different.

What's with that second point?

Yes, I like convenience. Well, say you're using a feed for getting updates on what your users do on your website. Sort of - an administration feed. Don't know about you, but I would like to have a nice little function that I can call anywhere in my module, give it all the data, and forget it. Say, on update of some certain content type I wanna post a notice to the admin feed. All I have to do is call one convenient function right there, and relax with Corona, or Guinness if it's winter.

Step 1. Preparing the RSS items to be published.

Let's create a table admin_feed that's gonna hold the feed items. We need 4 columns.

  • id - an integer (index)
  • type - a string (varchar possibly)
  • data - text
  • created - datetime/timestamp (should default to NOW)

Now let's open our module foo and add that single convenient function I was talking about. When I want to feed something, I want to specify what type of action is happening, and throw in some data as an object. This function will take my type, serialize my object, and save it to the db. Should be something like this:

<?php
   
function foo_feed_item($type, &$data = null) {
       
db_query("insert into admin_feed (type, data) values ('%s', '%s')", $type, serialize($data));
    }
?>

Alrighty. This was all the work necessary to get the feed items to save somewhere. Now the fun begins.

Step 2. Outputting feed.

Now it's time to display the feed. There should be a menu item that creates a path to our rss feed.

<?php
            $items
[] = array(    'path' => 'rss/admin',
                           
'title' => t('Admin Infostream'),
                           
'callback' => 'foo_feed_admin',
                           
'access' => true,
                           
'type' => MENU_CALLBACK
           
);
?>

This path expects the callback function foo_feed_admin() to exist, so lets create it. In the function we should take all of those saved items from admin_feed db table, and display them as a formatted feed. Here's my implementation, although you would probably want to make this feed private, and require a login:

<?php
   
function foo_feed_admin() {
       
$query = db_query('select * from admin_feed order by created desc');
       
$items = '';
       
        while (
$r = db_fetch_object($query))
           
$items .= theme('admin_feed', $r);
       
       
header(theme('feed_header'));
        echo
theme('feed_wrapper', $items);
    }
?>

I should mention two things about the above function. First of all, look at that loop. It goes through every item in the database, and passes it to the theme('admin_feed') function. We haven't written it yet, but getting there. Second of all, I have theme('feed_header') and theme('feed_wrapper') for my purposes, but you can make your own theme functions for that. It's important to see that I'm setting the header and using "echo" to output the result. This result is what you will see when you go to "rss/admin" path.

On to the theme now!

I will call my theme bar. So let's go to the sites/all/themes/bar directory. I have a template.php file there. Sometimes template.php grows too large with many theming functions for different things. This is why I prefer to split it into multiple files. Thus, for admin feed I created a file called theme_admin_feed.inc and included it into the template.php, but that's up to you. Now, let's create the first function - the one that will produce an RSS item.

<?php
   
function _make_item($title, $body, $link, $id) {
        return
format_rss_item(
           
$title,
           
$link,
           
$body,
            array(
               
'author' => 'nomail@something.com (admin)',
               
'guid' => $id
           
)
        );
    }
?>

This function returns a formatted item. Nothing to see here. You will see how we use it later.

The next function, however, will be interesting. This is the bar_admin_feed() function that we're calling from the module and passing the database rows to it. Here's what it looks like:

<?php
   
function bar_admin_feed( &$r ) {
        return
call_user_func_array('_af_'.$r->type, array( unserialize($r->data),  '['. strtoupper($r->type) .']', $r->id) );
    }
?>

This function uses php metaprogramming to call other functions in this file, depending on the type of the feed item. For example, if $r->type is "update", it will call _af_update() and pass those three parameters to it. I prefix '_af_' to prevent collisions with other functions in my template.php. It's up to you, but here "af" stands for "admin feed". Also I like to prefix all the titles of my admin feed with an uppercase type, wrapped into square brackets. So I would have something like "[UPDATE] User Johnny updated node 34". This helps if you want to apply coloring rules in your RSS reader later. So that's why I'm passing '['. strtoupper($r->type) .']' into my function. Now it's time to show how easy it has become to actually add those Update, Delete, Create notices to your program.

Step 3. Posting RSS items is now a piece of cake!

So now I want to post a feed item every time someone updates a node in Drupal, including the user who made the update, and whatever else I desire. I have my foo_nodeapi() where I catch the update action, the $node, etc. Here's what I'll do in there:

<?php
           
case 'update':
                global
$user;
               
$node->bywhom = $user;
               
foo_feed_item('update', $node);
                unset(
$node->bywhom);
            break;
?>

In this example I'm piggybacking the $user onto the $node, and passing it to foo_feed_item(). You could piggyback anything you want this way.

Now in our theme template.php (or in my case theme_admin_feed.inc) we have to write the theming function for this 'update' action. Here's how mine looks:

<?php
   
function _af_update(&$node, $title, $id) {
       
$title .= "$node->title was updated by $node->bywhom->name";
       
$body = "<a href='http://something.com/node/$node->nid/edit'>
                http://something.com/node/$node->nid/edit
                </a>
        "
;
       
$link = "http://something.com/node/$node->nid/edit";
       
        return
_make_item($title, $body, $link, $id);
    }
?>

As you can see, I have all the info conveniently provided to me. This is also the place where we're finally using that _make_item() function we wrote earlier. So I format my item anyway I want and pass it to _make_item(). I give myself some convenient links to the updated node, and the name of the person who made the update. All nice and tidy.

A few more words.

I'm sure there are tons of ways to accomplish this in a convenient way, and I would like to hear about them. If you see a better way to do something, a more clever technique, or anything, please let me know. I hope this is helpful. Any questions? Feel free to ask in the comments.

Dispatching AJAX requests with Drupal and JSON

1 :: Heed MENU_CALLBACK!

One of the frequently overlooked capabilities of Drupal is the so-called MENU_CALLBACK. Strangely, this straightforward menu-item type is among the unpopular ones. MENU_CALLBACK is simple. It only indicates that your defined path will be wired to some callback function in your module. Let's see how this works:

<?php
               
function mymodule_menu($may_cache) {
                    if (!
$may_cache) {
                       
$items[] = array(    'path' => 'json/dispatcher',
                                           
'title'    => t('Dispatcher of Ajax Requests'),
                                           
'callback' => 'mymodule_dispatcher',
                                           
'access' => true,
                                           
'type' => MENU_CALLBACK
                       
);
                    }
                   
                    return
$items;
                }
           
?>

The above snippet maps the path website.com/json/dispatcher to the function mymodule_dispatcher(). Everything that comes after the path will be passed into the function as parameters. For example if we used this URL: website.com/json/dispatcher/hello/world, and defined the function like this: function mymodule_dispatcher($var1, $var2) - then in the function $var1 would equal 'hello', and $var2 → 'world' respectively. This callback functionality is exactly what we need to create an AJAX+JSON event dispatcher.

2 :: Possibilities with callback function.

The trick to writing callback functions ( mymodule_dispatcher() in our case ) is in knowing the Drupal's framework. Commonly, Drupal is considered to be a CMS (Content Management System). Only when looking deeper, one would uncover the well-designed, powerful framework which makes all the surface functionality possible. If Drupal was a bicycle factory, and you were an engineer working on imroving the bicycles - you would be allowed to redirect the product into your own conveyer belt at any points during production. You could let the factory build the bicycle frame, then redirect it to your own conveyer belt to make your additions/alterations, after which you could let it back into regular production.

Alright, I'm getting a little ahead of myself there. In our module we will talk back to the client using drupal_to_js() function. This function accepts either an associative array or an object, and returns well-cooked JSON.

<?php
                $object
= new stdClass();
               
$object->var1 = 'hello';
               
$object->var2 = 'world';
               
$object->var3 = array( 1 => 'fluffy', 2 => 'horse' );
                echo
drupal_to_js($object);
           
?>

This snippet would output valid JSON, which is easily parsable by javascript.

3 :: Designing our dispatcher.

Dispatcher is there to dispatch. Let's follow the KISS principle and use PHP's switch() control statement.

<?php
               
function mymodule_dispatcher($action) {
                   
$out = new stdClass();
                    switch (
$action) {
                        case
'register':
                           
// handle registration, write output to $out
                       
break;
                        case
'login':
                           
// handle login, write output to $out
                       
break;
                        default:
                           
// handle nothing
                       
break;
                    }
                   
                   
header("Content-type: application/json");
                    echo
drupal_to_js($out);
                }
           
?>

With this simple dispatcher we can accept any URL such as website.com/json/dispatcher/[anything], and most importantly, nobody stops us from POSTing values into that URL. This means we can use Drupal's framework, and talk directly to client (bypassing theming). Javascript developer would only have to post values to your dispatcher, and work with returned JSON. Lets expand the function a little bit, using the login/register example.

<?php
               
function mymodule_dispatcher($action) {
                   
$out = new stdClass();
                    switch (
$action) {
                        case
'register':
                           
$out->messages = '';
                           
// assuming _nick_available() function is written elsewhere in the module
                           
if ( !_nick_available($_POST['name']) ) {
                               
$out->messages = array('name' => 'This username is already taken.');
                                break;
                            }
                           
                           
// this will process our POSTed values as if they were from Drupal's register form
                           
drupal_execute('user_register', $_POST);
                            if (
$errors = form_get_errors() ) {
                               
$out->messages = $errors;
                            }
                        break;
                       
                        case
'login':
                            global
$user;
                           
$out->messages = '';
                           
                            if ( !(
$user->uid > 0) )
                               
$user = user_authenticate( $_POST['user'], $_POST['pass'] );
                           
                            if ( !(
$user->uid > 0) )
                               
$out->messages = 'authentication failed';
                        break;
                       
                        case
'logout':
                           
user_logout();
                           
$out->messages = 'ok';
                        break;
                       
                        default:
                           
// handle nothing
                       
break;
                    }
                   
                   
drupal_get_messages(null, true); // this will clear drupal's message buffer
                   
header("Content-type: application/json");
                    echo
drupal_to_js($out);
                }
           
?>

As you can see - in the above example I used such Drupal framework's functions as drupal_execute(), form_get_errors(), user_authenticate(), and drupal_get_messages(). It shows once again that the hardest part about writing callbacks is in knowing which Drupal's functions to use to achieve the desired effect (without reinventing the bicycle).

4 :: Client Side

The only thing left here is for js developer to create forms with the expected fields. They would then ajax-submit them to our json/dispatcher/register, json/dispatcher/login, or json/dispatcher/logout, and receive the JSON response back from server, which they would then parse and use for any presentational purposes.

Hopefully this article clarified some of the practices we used in Scripteka. More stuff is coming so stay tuned!

1 reason I chose PHP over Ruby

There have been many debates on the subject, and many lists compiled. I personally think that PHP works better for me. In order to prove my point I decided to compile my own list of reasons as to why I made that choice.

1. I like the syntax better.

Syndicate content