Running views during hook_cron which require admin privileges

Update: 17/08/2011
* Thanks to Aron who commented below to recommend the session_save additions in case an error breaks execution while the user is switched. *

When needing to set up scheduled task in custom drupal modules, it is common to handle these with a hook_cron function. When this hook is triggered, it usually runs as the drupal anonymous user, but in some situations you want to run code during cron which requires certain higher level privileges.

A prime example of this are admin views. For example, on each cron run, you want a scheduled task to run on a set of nodes which match certain criteria. Instead of manually constructing a database query to find the matching nodes, it is a lot quicker and easier to use the views module to list the items you want to operate on, and execute this view in your cron function. This method ensures the module and query remain database-engine agnostic, and does not have to be updated when database table schemas change due to drupal updates.

The problem is, when you run cron, it is usually run as the anonymous user (if automatic), or as a specific user (if run manually). This user may not have access to either the view, or the nodes you are trying to list. To overcome this, we need to temporarily switch the current user to one with higher access (typically the admin user #1) while we execute the view.

The steps go like this:

  1. Get current drupal user uid and save as a temporary variable
  2. Get the current session save state, and save it as a variable. This is just to make sure we restore this to the right value when we are done.
  3. Disable session writing in case there is an error and the request ends before switching the user back.
  4. Set the current user to the admin user
  5. Execute the view and get some results. We can use a pre-existing view or define one directly in code.
  6. Re-load the original user using the saved user id
  7. Re-load the original session save state
  8. Process the results from your view

and here's the code for a cron hook which does just that.


function modulename cron() {
  //firstly we load the global variable for the current user
  global $user;
  
  if ($user && isset($user->uid)) {
    //We need a user id to be able to do this.
    //Anonymous user 0 will still be allowed here as the uid is set to zero
    
    //store the existing user and user id
    $original_uid = $user->uid;
  
    //Save the current user's session
    $old_session_save_state = session_save_session();
    session_save_session(FALSE);
  
    //set the current user to be the admin user.
    $admin_user_id = 1;
    //This assignment works because we have already declared the global 
    // $user variable in this function
    $user = user_load($admin_user_id);  
  
    //do your administrator tasks here. In this case, we are executing a view
    // and getting some results
    $view_results = views_get_view_result('my_view_name', 'my_display_id');

    //We are good Drupal citizens, so we reset the cron user to what we 
    // started with. This makes sure we don't break or change any other
    // cron permissions.
    $user = user_load($original_uid); 
    //we also reset the session save state to what it was before we started fiddling
    session_save_session($old_session_save_state ); 

    //now we can process the results of $view_results. Typically this will involve 
    // looping over the results array to do something with each resulting node id
  }
 }

An even better way to do this would be to create a re-usable function to return views results as run by an admin. Look for this in a future post. This principle can also equally well be applied to views of other objects like users or comments.

As far as security implications go, the risk is that a code error prevents the original user from being re-applied, and the rest of the cron runs as the admin. This may cause problems if you have other permission controlled code in your drupal cron tasks. However, any error should stop execution and only risks displaying the results of the page request up until that point. There is no risk of a logged-in user being switched to the admin, as the switch only happens for the remainder of the cron page request. Drupal works out and loads the current user on each page request via PHP sessions, so the next page request will be run as the correctly logged in user. I'd love to hear from anyone else who has opinions on any security problems with this approach?