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

usonian’s picture

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

patrickharris’s picture

Extremely useful - thanks!

rimian’s picture

Thanks! You made my job a little easier.

[Edit]: A lot easier

sime’s picture

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

lurkerfilms’s picture

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.

lurkerfilms’s picture

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.

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'?

yfreeman’s picture

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

jgadrow’s picture

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

yfreeman’s picture

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.

noobizness’s picture

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.

Gabriel R.’s picture

This is the method that worked for me.
Thanks!

drecute’s picture

  function acm_school_create_role($role) {
  module_load_include('inc', 'user', 'user.admin');

  if(!isset($role)) {
    return FALSE;
  }

  $q = $_GET['q'];
  $_GET['q'] = "";
  $form_id = "user_admin_role";
  $form_values = array();
  $form_values['op'] = t('Add role');
  $form_state = array();

  if(is_array($role)) {
    foreach($role as $key => $name) {
      $form_values['name'] = $name;
      $form_state['values'] = $form_values;
      drupal_execute($form_id, $form_state);
      $_GET['q'] = $q; 
    }
    return;
  }
  
  $form_values["name"] = $role;
  $form_state["values"] = $form_values;

  drupal_execute($form_id, $form_state);
  $_GET['q'] = $q; 
}
Craig Hughes’s picture

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.

yfreeman’s picture

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

wbarn’s picture

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.

izmeez’s picture

bookmarking this very useful thread

noobizness’s picture

robertjd’s picture

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?

yfreeman’s picture

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.

Ye’s picture

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


// 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);


seancharles’s picture

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.

yfreeman’s picture

Read the initial post toward the end....

Try this:

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

seancharles’s picture

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
:)

yfreeman’s picture

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

seancharles’s picture

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; }
lilwiki’s picture

$_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";
muja_dd’s picture

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

AndrewBoag’s picture

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

Sam5’s picture

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

hjc1710’s picture

This is a great post and is super-useful. But there is one minor note and it is here:

  $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;

This code works just fine, but if you're trying to add a role from within a form's submit handler using this code, then you'll get some issues.

Since the variable name of $form_state is generally used to hold all the information for a form, if you use this code from within your submit function, it will clear that variable and you will lose the submitted form's state and values. Thus, the original form cannot be submitted.

I figured this out because every time I went to submit a form that used this code, it redirected me back to the /admin/users/roles page, instead of where I needed to go. By changing the variable names, you can easily get around it. I used this code and weird redirects stopped happening (and my form would actually submit with values ;)).

function my_form_submit($form, &$form_state) {
  require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

  $form_id_role = "user_admin_new_role";
  $form_values_role = array();
		
  $form_values_role["name"] = $role_name;
  $form_values_role["op"] = t('Add role');
  $form_state_role = array();
  $form_state_role["values"] = $form_values_role;

  $q = $_GET['q'];
  $_GET['q'] = "";

  drupal_execute($form_id_role, $form_state_role);
			
  $_GET['q'] = $q;
  // Other form code goes here...
}

That should work fine and now you can add a role via the Form API while in another form's submit function!

shafiul’s picture

Is the method mentioned here also applicable for Drupal 7?
If not, please provide some help for Drupal 7.

Thanks

quicksketch’s picture

Drupal 7 thankfully solved this problem once and for all. There's simply the user_role_grant_permissions() function now. http://api.drupal.org/user_role_grant_permissions

Anonymous’s picture

To create a role use user_role_save().

pris54’s picture

The following also does the trick:
db_query("INSERT INTO {role} SET name = '%s'", 'Your new role');