If you’ve been following my blog, you know that I’ve been interested in Access Control Lists. In the previously mentioned article, I said some erroneous things. In a later article, I meant to correct them, but I was buzzing so much from excitement that all I managed to get out was “I did it.” Well, I did implement ACLs, and I did it without patching core.
This article isn’t really about ACLs, but it is about one of the things the ACL module does — which is to provide a widget to seamlessly edit the user list.
This widget can be included in a form with the following code:
<?php
$form['acl'] ="" acl_edit_form($acl_id, ‘test’);
?>That’s it. It handles both of the buttons, and it keeps track of the state of the list between page submits. Well, that’s not quite it. You also have to tell it to save your data in your form's _submit hook:
<?php
acl_save_form($form_values['acl']);
?>So how does this magic work? Well, I have to admit, it took me longer than I thought it would to figure it out, but it’s all about Forms API’s series of handlers and hooks. In this case, the form widget sets up an #after_build handler, which is the same handler that the node form uses when doing a preview.
What happens is that the form sets up some of the form elements above, compresses the actual user-list into a hidden field, and is done. During the hook, it checks to see if its buttons were pressed, and if they were it modifies the list in the hidden field, and moves it. If the list is empty it removes the unneeded elements (the checkboxes and the button to remove one) and it clears the add textfield if a user was added.
One thing: I couldn’t use the regular form submit button because it blasts the #name into ‘op’ which is great for most buttons, but I don’t want to actually use ‘op’. Why? Because there can be multiple copies of this form on the page, and if I did that, either only one could work, or there’d have to be some identifying text in the buttons. This way, the #value of the button will be changed and I can check it directly, and know that, for sure and absolutely, the right button was the one pressed.
Note that this code is not quite as generic as it really could be. It actually loads the user list in the same function as display, but it really could load the list and pass needed data into the form function. Doing that would generalize it so that it could (and very likely will) become part of a forms api widget library (which I believe drumm is working on.
<?php
/**
* Provide a special button type that doesn't get its #name blasted.
*/
function acl_elements() {
$type['acl_button'] = array('#input' => TRUE, '#button_type' => 'submit', '#form_submitted' => FALSE);
return $type;
}
function theme_acl_button($element) {
$element['#value'] = $element['#label'];
return theme('button', $element);
}
/**
* Provide a form to edit the ACL that can be embedded in other
* forms.
*/
function acl_edit_form($acl_id, $label = NULL) {
// Ensure the ACL in question even exists.
if (!$acl_name = db_result(db_query("SELECT name FROM {acl} WHERE acl_id = %d", $acl_id))) {
return $form;
}
if (!$label) {
$label = $acl_name;
}
$form = array(
'#type' => 'fieldset',
'#collapsible' => true,
'#title' => $label,
'#tree' => true);
$result = db_query("SELECT u.uid, u.name FROM {users} u LEFT JOIN {acl_user} aclu ON aclu.uid = u.uid WHERE acl_id = %d", $acl_id);
$users = array();
while ($user = db_fetch_object($result)) {
$users[$user->uid] = $user->name;
}
$form['acl_id'] = array('#type' => 'value', '#value' => $acl_id);
$form['deletions'] = array('#type' => 'hidden'); // placeholder
$form['delete_button'] = array(
'#type' => 'acl_button',
'#label' => t('Remove Checked')
);
$form['add'] = array(
'#type' => 'textfield',
'#title' => t('Add user'),
'#maxlength' => 60,
'#autocomplete_path' => 'user/autocomplete',
);
$form['add_button'] = array(
'#type' => 'acl_button',
'#label' => t('Add User'),
);
$form['user_list'] = array(
'#type' => 'hidden',
'#default_value' => serialize($users),
);
$form['#after_build'] = 'acl_edit_form_after_build';
return $form;
}
/**
* Process a form that had our buttons on it.
*/
function acl_edit_form_after_build($form, $form_values) {
// We can't use form_values because it's the entire structure
// and we have no clue where our values actually are. That's
// ok tho cause #value still works for us.
$user_list = unserialize($form['user_list']['#value']);
if ($form['delete_button']['#value'] && is_array($form['deletions']['#value'])) {
foreach($form['deletions']['#value'] as $uid) {
unset($user_list[$uid]);
}
}
else if ($form['add_button']['#value']) {
$name = $form['add']['#value'];
$uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $name));
if (!$uid) {
form_error($form['add'], "Invalid user.");
}
else {
$user_list[$uid] = $name;
$form['add']['#value'] = NULL;
}
}
if (count($user_list) != 0) {
$form['deletions']['#type'] = 'checkboxes';
$form['deletions']['#title'] = t("Current users");
$form['deletions']['#options'] = $user_list;
$form['deletions']['#value'] = array(); // don't carry value through.
// need $form_id and have no way to get it but from $_POST that
// I can find; and if we got here that variable's already been
// checked.
$form['deletions'] = form_builder($_POST['form_id'], $form['deletions']);
}
else {
$form['delete_button']['#type'] = 'value';
}
$form['user_list']['#value'] = serialize($user_list);
return $form;
}
?>

Drupal 5 status...
The Drupal 5 status of access control lists and this user-listing widget would be greatly appreciated.
I'm doing an access control module to work with the node relativity module and am looking for the best way to add users to, in my case, a node...
I was also looking at this:
autocomplete for multiple usernames?
http://drupal.org/node/82770
The ACL module is Drupal 5
As if Merlin would be developing in anything else in March of 2006... how silly of me.
Here's Merlin's ACL module Drupal project page.
Post new comment