Community & Support

howto - programmatically create roles in drupal 6

I've been using drupal for about 4 solid months now. I pretty much made it through the learnng curve. I've stumbled upon some things that may be valuable to the community.

Surprisingly there seems to be an absence of material relating creating things programatically in drupal, such as roles, permissions and install profiles.

After looking for material related to programmatically creating roles in drupal and coming up with nothing, I had to resort to running the drupal code through the debugger and decipher how to do things programatically.

This little post explain how to programmatically create roles in drupal without directly touching the database and letting drupal take care of all the dirty work

The following code creates a new role in drupal.

require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

$form_id = "user_admin_new_role";
$form_values = array();
$form_values["name"] = "new role";
$form_values["op"] = t('Add role');
$form_state = array();
$form_state["values"] = $form_values;

drupal_execute($form_id, $form_state);

here is an explanation of the code above.

We are basically mimicking the "Add new role" form that is available to the administrator of the site. This is done by filling in values and running the form. We need to do this because the code that creates the the new role is in the submit handler of the form. see user_admin_role_submit()


require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

we need the above line of code to load the proper file that contains the callbacks for the form, which is not necessarily loaded.

$form_id = "user_admin_new_role";
You need to name the form as follows. even though there are no direct handlers for such a form , the users_forms() function takes care of that for us.

$form_values = array();
$form_values["name"] = "new role";
$form_values["op"] = t('Add role');
$form_state = array();
$form_state["values"] = $form_values;

the above code fills in the form values.

you must use the t() function for
$form_values["op"] = t('Add role');
or the form wont be processed

drupal_execute($form_id, $form_state);

we then execute the form.

This all works fine and dandy when it is executed in a script or whatever, however when I ran the code during an installation of a module, it would stop working, the form handlers were never called.

After spending hours running through the code again and again I realized that the the form is generated based on the the URL. or $_GET['q'] value. see user_admin_role()
I don't know if this was done this way for a specific purpose but I hope in the next release the developers can give is either a function to directly create roles or not generate the form based on the url.

Anyways to circumvent this issue just swap the code for the following

$q = $_GET['q'];
$_GET['q'] = "";
drupal_execute($form_id, $form_state);
$_GET['q'] = $q;

If you want to find out what the role ID is of the role you just created, I use the following to get the trick done.

$role = "new role"; // or any role name that you want the role id for
$roles_table = user_roles(); // loads the roles table into an array
$rid = array_search($role, $roles_table); // where $rid == the role id of the $role variable name.

Comments

The $_GET['q'] fix works for Drupal 5 too

Very interesting, thanks much for taking the time to share your notes. I ran into the same problem trying to create a role in a Drupal 5 module's implementation of hook_install(); the form never got processed. I was about to run it through a debugger when I found this page.

Temporarily emptying $_GET['q'] before calling drupal_execute() did the trick in Drupal 5 too. I still don't grok what's going here 100%, but perhaps it's related to this thread, which suggests overriding a form's #redirect property before running it through drupal_execute(): http://drupal.org/node/145146

Extremely useful - thanks!

Extremely useful - thanks!

Fantastic

Thanks! You made my job a little easier.

[Edit]: A lot easier

I highly recommend anyone

I highly recommend anyone interested in this to download and browse the .inc files in Install Profile API

Install Profile API

I agree it is better to have an API but the install_profile_api (although convenient) is duplicating core functionality and bypassing the object abstract layer. They are making direct table inserts based on a particular core release. This means it has to be in lock step with what core has implemented.

Yes this works but I would rather have a User object or set of procedures that implement the common functionality that was in core. We would take advantage of all the safety checks without duplication.

In the next post I show an example using the existing core form API (I don't believe there is any side effects). This uses cores validate and submit methods in the most direct way currently available in core without duplicating the code.

It is a bit clumsy but predictable. Hopefully the install_profile_api will be pushed into core and re-factored.

I am not saying don't use install_profile_api... just beware of the trade offs.

Andrew Migliore
Lurker Films Inc.
http://www.lurkerfilms.com

A simpler method?

Why not just use the validate and submit methods? This gets around the whole issue of redirect et. al. and the convoluted processing of forms.

<?php
require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

function
doit() {
       
form_set_error(null, null, TRUE); // clear out any old errors

       
$form_state = array( "values" => array( "op" => t('Add role'), "name" => "MY ROLE" ) );
       
user_admin_role_validate(null, $form_state);
       
$form_errors = form_get_errors();
        if (empty(
$form_errors )) {
               
user_admin_role_submit(null, $form_state);
        }       
}
?>

This is the meat of what you want to do. It uses the validate and submit methods for data integrity.

And why doesn't drupal use constants for things like 'Add role'?

Andrew Migliore
Lurker Films Inc.
http://www.lurkerfilms.com

May not be simpler....

I believe the validation function are called automatically when submitting the form via
drupal_execute()

when forms are submitted the validation and submission functions are called automatically.

so with drupal_execute() you are calling
user_admin_role_validate() and user_admin_role_submit() in the background.

But the addition of the form error control is useful if you want to print out the form errors

Complete usage (install / uninstall)

Here's an alternate method in case you need to add / remove multiple roles. If anyone has a better solution, please let me know as I just whipped this together to provide some baseline functionality for some derived modules.

<?php
/**
* Implementation of hook_install().
*/
function hook_install() {
  // Create query parameters
  $params = _hook_get_roles_by_name ();
  array_unshift ($params, 'INSERT INTO {role} (name) VALUES (\'%s\')' . str_repeat (', (\'%s\')', count ($params) - 1));
 
  // add roles to system
  call_user_func_array ('db_query', $params);
}

/**
* Implementation of hook_uninstall().
*/
function hook_uninstall() {
  // Create query parameters
  $params = _hook_get_roles_by_id ();
  $numRoles = count ($params) - 1;
  array_unshift ($params, true);
 
  $querySet = array (
    'DELETE FROM {role} WHERE rid = %d' . str_repeat (' || rid = %d', $numRoles),
    'DELETE FROM {permission} WHERE rid = %d' . str_repeat (' || rid = %d', $numRoles),
    'DELETE FROM {users_roles} WHERE rid = %d' . str_repeat (' || rid = %d', $numRoles),
  );

  // Perform queries to remove role data
  while ($query = array_shift ($querySet)) {
    $params [0] = $query;
    call_user_func_array ('db_query', $params);
  }
}

function _hook_get_roles_by_name() {
  // Return role information
  return array (
    'Role name #1',
    'Role name #2',
  );
}

function _hook_get_roles_by_id () {
    // Create query parameters
    $params = _hook_get_roles_by_name ();
    array_unshift ($params, 'SELECT rid FROM {role} WHERE name = \'%s\'' . str_repeat (' || name = \'%s\'', count ($params) - 1));
   
    // retrieve roles from system
    $result = call_user_func_array ('db_query', $params);
    $role = array ();
   
    while ($row = db_fetch_array ($result)) {
      $role[] = $row ['rid'];
    }
   
    return $role;
}

If you have an editor that supports regular expressions, the following will allow you to replace all relevant 'hook' portions with your module hook:
^(.[^*].*)hook

When replacing, be sure you prepend the first captured subpattern to your module hook or you'll be missing a lot. ;)

direct database manipulation

Well done!

You method manipulates the database directly.
The first method uses underlying Drupal functionality to do the same thing.

I guess depending on what the programmer prefers they can choose from the two different methods.

Add some bumpers...

Database manipulation (or role addition with functions) have a few concerns:

* Make sure roles don't already exist before you add them.
* Consider the wisdom of removing the roles on uninstall. They don't do any harm just being there and they might end up being suborned through use to include other useful privileges. So the argument could be made that you are potentially erasing useful data in the course of erasing harmless data.

works for me WFM!

This is the method that worked for me.
Thanks!

How to delete the roles again

Thanks! Awesomely useful info. I also needed to delete the roles programmatically sometimes, and came up with this, which works the same way:

$q = $_GET['q'];

require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

$roles_table = user_roles(); // loads the roles table into an array

$form_id = "user_admin_role";
$form_state = array(
'values' => array(
'op' => t('Delete role'),
),
);

foreach($my_roles_array as $role)
{
$rid = array_search($role, $roles_table);
drupal_set_message(t("Removing role: ").$role);
$form_state['values']['name'] = $role;
$form_state['values']['rid'] = $rid;
$_GET['q'] = "admin/user/roles/edit/$rid";
drupal_execute($form_id, $form_state);
}

$_GET['q'] = $q;

Note that when deleting roles, the function user_admin_role() in core's user.admin.inc requires $_GET['q'] to be set such that arg(4) is a role ID, so you can't just set $_GET['q'] to "", but need to actually pass something useful there.

Beautiful

Excellent code. Drupal takes care of the heavy work of removing the roles from the users.

Delete role using Rules action

Is there a way to modify this code to run as part of a Rules action? I am trying to have it so that when you delete a node of a specific content type, which created a corresponding role when it was created, delete the corresponding role if there is one.

bookmarking this very useful

bookmarking this very useful thread

solved -- in module form

form mimicking or direct queries?

The Permissions API has the right idea, but it's still working directly with the DB. I still prefer the first method in this thread (form mimicking) because it is not touching the DB. But will this form-mimicking method survive drupal core upgrades any better than a method which would be affected by core schema changes?

It should stay safe within versions

This technique should be good in the same major release. i.e. D6, but in D7 things may be different. Perhaps there will be an update if when D7 is released.

I'm also aware of some sort of CRUD (Create Retrieve Update Delete) for core modules. Which should solve this problem entirely.

I was stumbled upon this question recently as well and came across this method in API doc. It seems that the orthodox Drupal way (at least in version 6.x) is to interface the core functionality via Form API, Drupal official API documentation provided two examples, one is to create user, the other is to create user roles via drupal_excute_form():

UPDATE: this is exactly what yfreeman pointed out at first, but I just wanted to point out that the same technique can be applied more than creating users / roles / nodes / etc. Kudos to yfreeman :)

http://api.drupal.org/api/function/drupal_execute/6

<?php
// register a new user
$form_state = array();
$form_state['values']['name'] = 'robo-user';
$form_state['values']['mail'] = 'robouser@example.com';
$form_state['values']['pass']['pass1'] = 'password';
$form_state['values']['pass']['pass2'] = 'password';
$form_state['values']['op'] = t('Create new account');
drupal_execute('user_register', $form_state);

// Create a new node
$form_state = array();
module_load_include('inc', 'node', 'node.pages');
$node = array('type' => 'story');
$form_state['values']['title'] = 'My node';
$form_state['values']['body'] = 'This is the body text!';
$form_state['values']['name'] = 'robo-user';
$form_state['values']['op'] = t('Save');
drupal_execute('story_node_form', $form_state, (object)$node);
?>

Permissions during module installation?

Hi,

I am writing a big Drupal application and I want to make the .install file create everything. I have some roles that I want to create and based upon code in this post I wrote this as a helper function in my .install file:-

function _soil_create_role( $role_name ) {
  require_once(drupal_get_path('module','user') . '/user.admin.inc');
  $fs = array(
    'values' => array(
      'name' => $role_name,
      'op'   => t('Add role')));
  drupal_execute('user_admin_new_role', $fs);
  return
    db_result( db_query(
      "SELECT rid FROM {role} WHERE name = '%s'",
      $role_name ));
}

However, it fails every time to create a role during the module installation. It works if I call it from index.php (I require the .install file then call __soil_create_role() ) and I cannot see what would cause it *not* to work during the install process.

It is driving me frikkin' nuts. I've checked permissions regarding role creation and there aren't any I can see. I have ruled out permissions. The other problem I have is that constants defined in the .module file do not appear to be valid during the .install phase either, I tried to use them to create the role name and I ended up with the name of the constant as upper-case instead of the value.

To eliminate scope issues I created a small function in .module and .install ...
function foo() {}
and the resulting errors (cannot define foo) told me that the .module file had beed parsed before the .install file but I cannot therefore see why they constants had no value.

If anybody can help me out I'd be very grateful. I want to create several other roles at install time and also to set the permissions for them, all from the code and so far I am getting a bit frustrated. I want to know that after the module has installed, it is locked and loaded and all dependencies regarding roles etc are already in place.

Thanks,
Sean Charles.

read inital post

Read the initial post toward the end....

Try this:

$q = $_GET['q'];
$_GET['q'] = "";
drupal_execute('user_admin_new_role', $fs);
$_GET['q'] = $q;

Good God. I can't believe I

Good God. I can't believe I thought I'd read it and understood it.
Talk about not seeing the wood for the tree.
Thanks!
OOPS
:)

I went through the same thing

I went through the same thing... thats why I put together the post. Hopefully you have some hair left. ;)

Yes.... lots but going

Yes.... lots but going grey!

I modified my code today and came up with this little helper, thanks yfreeman very much!
:)

/**
* HELPER: Programatically execute a form.
*/
function _soil_install_execform( $form_name, $form_state ) {
  $q = $_GET['q'];
  $_GET['q'] = "";
  drupal_execute( $form_name, $form_state );
  $_GET['q'] = $q; }

Flawed helper

$_GET['q'] should not always be an empty string.

You should modify your helper to something like this.

/**
* HELPER: Programatically execute a form.
*/
function _soil_install_execform( $form_name, $form_state, $qval="") {
  $q = $_GET['q'];
  $_GET['q'] = $qval;
  drupal_execute( $form_name, $form_state );
  $_GET['q'] = $q;
}

e.g. If deleting a role

// $rid = role id
$_GET['q']="admin/user/roles/edit/$rid";

thanks !!

Thanks for sharing this!!! This is really useful.

muj

Also there is the Features Module

This has been an interesting read as we struggle with this requirement for one of our Drupal sites. I just got back from Drupal Down Under in Brisbane (Jan 2011) and one of the things being talked about was this sort of configuration management.

Of the approaches outlined above, I would probably shy away from the direct database approach as this looks a little scary.

There is also the features module that allows you to export roles. This way they can be included in version control code. see http://drupal.org/project/features

Thanks a Lot

Thanks a Lot... It is very useful...