Last updated April 9, 2012. Created by neuronomicon on December 9, 2009.
Edited by shamio. Log in to edit this page.

Aim

To set up a Drupal site so that it can deliver a series of tutorials to subscribers over a period of time, such that in the first week of the subscription the subscriber receives tutorial one, in the second week, tutorial two and so on.

Modules used

role_watchdog (role_watchdog keeps a record of the times that a user role changes, something that Drupal does not otherwise track. This will enable us to use user roles to determine who is subscribed to a given tutorial. The other advantage is that it records when a role is removed and/or re-instated, so with a little extra work it is possible to keep a returning subscriber at the some point in the tutorial flow as when they left. In this scenario, Jo Bloggs signs up for your 14 week course but after 7 weeks unsubscribes... 6 months later he decides to continue, and when their user role is reapplied, the coourse automatically continues where it left off). Note... role_watchdog does not seem to track the addition of a role if it is added when the user is created, so for this approach to work, you must first allow your subscribers to become users, and THEN assign the role that will give them access to the tutorial.

Views PHP filter. Used to run a php snippet which will create a menu from a block that will only consist of links to the tutorials the user is allowed access to. Views PHP Filter appears to have some difficulty in the cases where there are NO matches, so it is worth creating a default page to send non-subscribers to, so that we can pass that as a match in the array for Views PHP Filter, rather than sending a null array.

CCK. We need some dedicated fields in the tutorial pages.

content_access: just to ensure that only people who have the relevant role assigned to them can see your tutorials

Method

The first thing to do is to install the four modules above and to activate them.

Create a role to assign to your tutorial students, so that we can identify them... in this example "Communication Skills Student"

Then we need to create a CCK node (a new content type) to hold each of our tutorial lessons (although not tested at the time of writing, this system should be able to manage multiple tutorials)

Let's create a content type called "communication_skills_tutorial". The important field to include is an integer field that will be used to store the period of time after which a given tutorial page will become available. I've called mine "field_release_tutorial_after" and the php snippet to be used later will evaluate this in terms of days. Once you've created this, you can go to the edit section and select "Access Control" where you can make the content viewable only to users with the roles you assign.

For testing purposes then, lets create a few pages of content type "communication_skills_tutorial", starting with Communications Skills Tutorial One to be made available after 0 days, Tutorial 2 to be made available after 7 days and so on. If you don't have a test user already, create one, and then assign the role "Communication Skills Student" to them.

I also strongly recommend creating a default page that will show in the menu if it is viewed by anyone who has no access to any tutorials. You can use any content type, and I'll use a page and give it the title "Introduction to Communication Skills".

Now we go to views and create a default view which we shall call "communication_skills_menu", displaying items of type Node. From the fields option, add Node:Title field and configure it to display as a link to the node.

Now go to the filter option and select Node: Node ID PHP handler. On configuration, make sure you have selected Operator:OR, Handler: PHP Code, and copy the following code (without <? and ?> tags) into the PHP text area

global $user;
$contenttype = 'communication_skills_tutorial';
$checkagainstfield = 'field_release_tutorial_after';
$checkforrole = 'Communication Skills Student';
$defaultnodetitle= AddSlashes('Introduction to Communication Skills');
$myquery="SELECT nid FROM node WHERE title = '$defaultnodetitle'";
$defaultnid=db_result(db_query($myquery));
//if we have a logged in user
if($user->uid){
    //aid is the designation that role_watchdog uses for the userID of the user associated with the role
    $aid=$user->uid;
    $myquery="SELECT rid FROM role WHERE name = '$checkforrole'";
    $rid=db_result(db_query($myquery));
    $myquery="SELECT COUNT(*) FROM role_watchdog WHERE aid = '$aid' AND rid='$rid'";
    $countrows = db_result(db_query($myquery));
    //no point in proceeding unless we return some results here:-)
    if($countrows){
        $myquery="SELECT stamp, action FROM role_watchdog WHERE aid = '$aid' AND rid='$rid' ORDER BY stamp ASC";
        $rolehistory=db_query($myquery);
        $i=0;
        $totaltime=0;
        while($mywatchdog=db_fetch_object($rolehistory)){
            if($i==0 AND $mywatchdog->action==0){
                //ignore
                $i++;
            }
            else{
                if($mywatchdog->action==0){
                    $totaltime=$totaltime+$mywatchdog->stamp;
                }
                else{
                    $totaltime=$totaltime-$mywatchdog->stamp;
                }
            }
            $lastaction=$mywatchdog->action;
        }
        if($lastaction==1){
            $totaltime=time()+$totaltime;
            $totaltimedays=floor($totaltime/(24*60*60));
            $myquery="SELECT nid FROM content_type_$contenttype WHERE ".$checkagainstfield."_value<='$totaltimedays'";
            $nidstomatch=db_query($myquery);
            $nids=array();
            while($nid=db_result($nidstomatch)){
                $nids[]=$nid;
            }
        }
        else{
            //the last action was to remove the role so we can't let them proceed anyway
            $nids=array($defaultnid);
        }
    }
    else{
        $nids=array($defaultnid);
    }
}
else{
    $nids=array($defaultnid);
}
return $nids;

Update the filter, and then save the view. Add the Block display to the view and save. You can now place this block where you will as a menu that will link only to those tutorials that a logged in and subscribed user can see... anyone else will only see the default page link, where you can extol the virtues of signing up to your courses :-)

Further refinements: Currently, if your URLs follow a predictable pattern, someone could guess the url of future tutorials and gain access to them. You could either get around that by using random elements in your urls, but it would be more satisfactory to simply deny access using similar code to the above to decide whether access was permitted.

This can be accomplished by using the above code in the body field of each tutorial node (but this time between <? ?> tags) and setting it the Input Format to PHP Code, and adding the following code after the above but before the ?>

$thisnid=split("/",$_GET['q']);
if (!in_array($thisnid[1], $nids)) {
  header("Location: /your_error_page");
  exit();
}

This will redirect any pages the user is not entitled to see to (in this case) yoursite/your_error_page, which you can set to any error message you want. Obviously, you can change the redirect location.

To be done
For more generic use and the ability to easily set up multiple tutorials, it would make sense to use a generic tutorial content type with a taxonomy vocabulary assigned to it, each term of which would define which tutorial this was... it should then be possible to set up a generic tutorial menu which would not have to reference all the above variables.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.