howto - programmatically create roles in druapl 6

yfreeman - July 16, 2008 - 06:52

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.

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

usonian - December 2, 2008 - 00:28

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!

patrickharris - December 5, 2008 - 01:46

Extremely useful - thanks!

Fantastic

rimian - February 4, 2009 - 11:54

Thanks! You made my job a little easier.

[Edit]: A lot easier

I highly recommend anyone

sime - February 4, 2009 - 13:56

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

Install Profile API

lurkerfilms - March 7, 2009 - 00:40

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?

lurkerfilms - March 7, 2009 - 00:29

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....

yfreeman - March 8, 2009 - 02:57

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)

jgadrow - June 30, 2009 - 20:22

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

yfreeman - July 1, 2009 - 19:03

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.

How to delete the roles again

Craig Hughes - August 20, 2009 - 23:49

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

yfreeman - August 27, 2009 - 16:17

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

bookmarking this very useful

izmeez - December 1, 2009 - 06:47

bookmarking this very useful thread

 
 

Drupal is a registered trademark of Dries Buytaert.