| 1 |
<?php
|
| 2 |
// $Id: aclfield.module,v 1.4 2007/02/13 19:18:23 mfredrickson Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Defines simple text field types.
|
| 7 |
*/
|
| 8 |
|
| 9 |
/**
|
| 10 |
* Implementation of hook_field_info().
|
| 11 |
*/
|
| 12 |
function aclfield_field_info() {
|
| 13 |
return array(
|
| 14 |
'aclfield' => array('label' => 'ACL Field'),
|
| 15 |
);
|
| 16 |
}
|
| 17 |
|
| 18 |
/**
|
| 19 |
* Implementation of hook_field_settings().
|
| 20 |
*/
|
| 21 |
function aclfield_field_settings($op, $field) {
|
| 22 |
switch ($op) {
|
| 23 |
case 'form':
|
| 24 |
return;
|
| 25 |
|
| 26 |
case 'save':
|
| 27 |
return;
|
| 28 |
|
| 29 |
case 'database columns':
|
| 30 |
$columns = array(
|
| 31 |
'realm' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
|
| 32 |
'grants' => array('type' => 'int', 'not null' => FALSE, 'default' => NULL, 'sortable' => TRUE),
|
| 33 |
'module' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
|
| 34 |
'data' => array('type' => 'longtext', 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
|
| 35 |
);
|
| 36 |
|
| 37 |
return $columns;
|
| 38 |
|
| 39 |
case 'filters':
|
| 40 |
// TODO a filter such as "nodes where XXX grant can YYY priviledge"
|
| 41 |
// example: Group Administrators can Read, Edit, Delete
|
| 42 |
|
| 43 |
/*
|
| 44 |
return array(
|
| 45 |
'default' => array(
|
| 46 |
'list' => $allowed_values,
|
| 47 |
'list-type' => 'list',
|
| 48 |
'operator' => 'views_handler_operator_or',
|
| 49 |
'value-type' => 'array',
|
| 50 |
),
|
| 51 |
);
|
| 52 |
*/
|
| 53 |
break;
|
| 54 |
}
|
| 55 |
}
|
| 56 |
|
| 57 |
/**
|
| 58 |
* Implementation of hook_field().
|
| 59 |
*/
|
| 60 |
function aclfield_field($op, &$node, $field, &$items, $teaser, $page) {
|
| 61 |
switch ($op) {
|
| 62 |
case 'load':
|
| 63 |
// TODO make cck respect my load directives!
|
| 64 |
break;
|
| 65 |
}
|
| 66 |
}
|
| 67 |
|
| 68 |
/**
|
| 69 |
* Implementation of hook_field_formatter_info().
|
| 70 |
*/
|
| 71 |
function aclfield_field_formatter_info() {
|
| 72 |
return array(
|
| 73 |
'default' => array(
|
| 74 |
'label' => 'Default',
|
| 75 |
'field types' => array('aclfield'),
|
| 76 |
),
|
| 77 |
);
|
| 78 |
}
|
| 79 |
|
| 80 |
/**
|
| 81 |
* Implementation of hook_field_formatter().
|
| 82 |
*
|
| 83 |
* The $node argument is necessary so that filter access can be checked on
|
| 84 |
* node preview.
|
| 85 |
*/
|
| 86 |
function aclfield_field_formatter($field, $item, $formatter, $node) {
|
| 87 |
$data = unserialize($item['data']);
|
| 88 |
$perms = array (
|
| 89 |
0 => t('No permissions'),
|
| 90 |
1 => t('View'),
|
| 91 |
2 => t('View & Edit'),
|
| 92 |
3 => t('View, Edit & Delete'),
|
| 93 |
);
|
| 94 |
|
| 95 |
$processed = call_user_func($item['module'] . '_node_grants_display', $item['realm'], $data);
|
| 96 |
switch ($formatter) {
|
| 97 |
|
| 98 |
default:
|
| 99 |
|
| 100 |
return theme('aclfield_content_permissions', $item['module'], $item['realm'], $processed, $perms[$item['grants']], $data);
|
| 101 |
}
|
| 102 |
}
|
| 103 |
|
| 104 |
function theme_aclfield_content_permissions($module, $realm, $processed, $grants, $data) {
|
| 105 |
$module_html = "<span class = \"aclfield-who aclfield-who-$module aclfield-who-$module-$realm\">$processed</span>";
|
| 106 |
$may = "<span class = \"aclfield-may aclfield-may-$module aclfield-may-$module-$realm\">" . t('may') . "</span>";
|
| 107 |
$grants_html = "<span class = \"aclfield-grants aclfield-grants-$module aclfield-grants-$module-$realm\">$grants</span>";
|
| 108 |
return "$module_html $may $grants_html";
|
| 109 |
}
|
| 110 |
|
| 111 |
/**
|
| 112 |
* Implementation of hook_widget_info().
|
| 113 |
*/
|
| 114 |
function aclfield_widget_info() {
|
| 115 |
return array(
|
| 116 |
'aclfield_standard' => array(
|
| 117 |
'label' => 'Standard',
|
| 118 |
'field types' => array('aclfield'),
|
| 119 |
),
|
| 120 |
'aclfield_hidden' => array(
|
| 121 |
'label' => 'Hidden from Non-Administrators',
|
| 122 |
'field types' => array('aclfield'),
|
| 123 |
),
|
| 124 |
);
|
| 125 |
}
|
| 126 |
|
| 127 |
/**
|
| 128 |
* Implementation of hook_widget_settings().
|
| 129 |
*/
|
| 130 |
function aclfield_widget_settings($op, $widget) {
|
| 131 |
|
| 132 |
switch ($op) {
|
| 133 |
case 'form':
|
| 134 |
// the add button was being saved for some reason...
|
| 135 |
unset($widget['default_perms']['add_button']);
|
| 136 |
// the delete button is also being saved
|
| 137 |
if (is_array($widget['default_perms']['perms'])) {
|
| 138 |
foreach(array_keys($widget['default_perms']['perms']) as $key) {
|
| 139 |
unset($widget['default_perms']['perms'][$key]['delete']);
|
| 140 |
}
|
| 141 |
}
|
| 142 |
|
| 143 |
$form = array();
|
| 144 |
$form['default_perms'] = array (
|
| 145 |
'#type' => 'aclfield',
|
| 146 |
'#title' => t('Defaults'),
|
| 147 |
'#default_value' => $widget['default_perms'],
|
| 148 |
'#multiple' => true, // TODO should tie to field...
|
| 149 |
);
|
| 150 |
$form['collapsed'] = array (
|
| 151 |
'#type' => 'checkbox',
|
| 152 |
'#title' => t('Collapse?'),
|
| 153 |
'#default_value' => $widget['collapsed'],
|
| 154 |
'#description' => t('You may hide the body of this field in a collapsible fieldset. This is useful to combine with default values so that users are not presented with information they need not change.'),
|
| 155 |
);
|
| 156 |
return $form;
|
| 157 |
|
| 158 |
case 'save':
|
| 159 |
return array('default_perms', 'collapsed');
|
| 160 |
}
|
| 161 |
}
|
| 162 |
|
| 163 |
|
| 164 |
/**
|
| 165 |
* Implementation of hook_widget().
|
| 166 |
*/
|
| 167 |
function aclfield_widget($op, &$node, $field, &$items) {
|
| 168 |
|
| 169 |
switch ($op) {
|
| 170 |
case 'prepare form values':
|
| 171 |
// only override the items w/ the defaults if this is a new node
|
| 172 |
if (is_array($field['widget']['default_perms']['perms']) && !isset($node->nid)) {
|
| 173 |
$items = $field['widget']['default_perms']['perms'];
|
| 174 |
}
|
| 175 |
// unserailzed the data from the items
|
| 176 |
foreach(array_keys($items) as $key) {
|
| 177 |
// TODO move this to the field level?
|
| 178 |
// we have to check if a string, in case it came from defaults
|
| 179 |
if (is_string($items[$key]['data'])) {
|
| 180 |
$items[$key]['data'] = unserialize($items[$key]['data']);
|
| 181 |
}
|
| 182 |
// remove the deleted button if it is saved as part of the defaults
|
| 183 |
unset($items[$key]['delete']);
|
| 184 |
}
|
| 185 |
|
| 186 |
break;
|
| 187 |
case 'form':
|
| 188 |
|
| 189 |
switch($field['widget']['type']) {
|
| 190 |
|
| 191 |
case 'aclfield_hidden':
|
| 192 |
// if the user doesn't have admin nodes, give back a value instead of a form
|
| 193 |
if (!user_access('administer nodes')) {
|
| 194 |
$form[$field['field_name']] = array('#tree' => TRUE);
|
| 195 |
$form[$field['field_name']][]['perms'] = array(
|
| 196 |
'#type' => 'value',
|
| 197 |
'#value' => $items,
|
| 198 |
);
|
| 199 |
return $form;
|
| 200 |
}
|
| 201 |
// if the user does have administer nodes, he/she gets to see the form
|
| 202 |
case 'aclfield_standard':
|
| 203 |
$widget = array(
|
| 204 |
'#type' => 'aclfield',
|
| 205 |
'#title' => $field['widget']['label'],
|
| 206 |
'#multiple' => $field['multiple'],
|
| 207 |
'#default_value' => array(
|
| 208 |
'perms' => $items,
|
| 209 |
),
|
| 210 |
'#collapsible' => true,
|
| 211 |
'#collapsed' => $field['widget']['collapsed'],
|
| 212 |
'#description' => $field['widget']['description'],
|
| 213 |
);
|
| 214 |
break;
|
| 215 |
}
|
| 216 |
|
| 217 |
$form[$field['field_name']] = array('#tree' => TRUE);
|
| 218 |
$form[$field['field_name']][] = $widget;
|
| 219 |
return $form;
|
| 220 |
|
| 221 |
case 'validate':
|
| 222 |
// TODO check for required
|
| 223 |
break;
|
| 224 |
|
| 225 |
case 'process form values':
|
| 226 |
$perms = array();
|
| 227 |
if(is_array($items[0]['perms'])) {
|
| 228 |
$perms = $items[0]['perms'];
|
| 229 |
}
|
| 230 |
$items = array();
|
| 231 |
foreach($perms as $perm) {
|
| 232 |
$items[] = array (
|
| 233 |
'realm' => $perm['realm'],
|
| 234 |
'grants' => $perm['grants'],
|
| 235 |
'module' => _aclfield_map_grant_to_module($perm['realm']),
|
| 236 |
'data' => serialize($perm['data']), // TODO move this to the field level?
|
| 237 |
);
|
| 238 |
}
|
| 239 |
|
| 240 |
break;
|
| 241 |
}
|
| 242 |
}
|
| 243 |
|
| 244 |
function _aclfield_get_grant_types() {
|
| 245 |
static $types;
|
| 246 |
if ($types) return $types;
|
| 247 |
|
| 248 |
$types = array();
|
| 249 |
$modules = module_implements('node_grants_info');
|
| 250 |
foreach($modules as $module) {
|
| 251 |
$res = module_invoke($module, 'node_grants_info');
|
| 252 |
$types[$module] = $res;
|
| 253 |
}
|
| 254 |
|
| 255 |
return $types;
|
| 256 |
}
|
| 257 |
|
| 258 |
function _aclfield_map_grant_to_module($grant) {
|
| 259 |
$grants = _aclfield_get_grant_types();
|
| 260 |
foreach($grants as $module => $mod_grants) {
|
| 261 |
foreach($mod_grants as $realm => $label) {
|
| 262 |
if ($realm == $grant) { return $module; }
|
| 263 |
}
|
| 264 |
}
|
| 265 |
}
|
| 266 |
|
| 267 |
function aclfield_elements() {
|
| 268 |
$type['aclfield'] = array(
|
| 269 |
'#input' => TRUE,
|
| 270 |
'#process' => array('aclfield_process_element' => array()),
|
| 271 |
);
|
| 272 |
|
| 273 |
return $type;
|
| 274 |
}
|
| 275 |
|
| 276 |
function aclfield_process_element($element) {
|
| 277 |
// TODO delete button
|
| 278 |
//dprint_r($element['#value']);
|
| 279 |
|
| 280 |
$perms = array();
|
| 281 |
$grant_types = _aclfield_get_grant_types();
|
| 282 |
$element['#tree'] = true;
|
| 283 |
|
| 284 |
if(is_array($element['#value']['perms'])) {
|
| 285 |
foreach($element['#value']['perms'] as $key => $perm) {
|
| 286 |
if ($element['#value']['perms'][$key]['delete']) {
|
| 287 |
// uncollapse the form
|
| 288 |
$element['#collapsed'] = false;
|
| 289 |
continue; // skip deleted items
|
| 290 |
}
|
| 291 |
$perms[$key] = array (
|
| 292 |
'realm' => $perm['realm'],
|
| 293 |
'grants' => $perm['grants'],
|
| 294 |
'module' => $module,
|
| 295 |
'data' => $perm['data'],
|
| 296 |
'module' => _aclfield_map_grant_to_module($perm['realm']),
|
| 297 |
);
|
| 298 |
}
|
| 299 |
|
| 300 |
unset($element['#value']['perms']);
|
| 301 |
}
|
| 302 |
|
| 303 |
// add button was pressed
|
| 304 |
if ($element['#value']['add_button']) {
|
| 305 |
$grant = $element['#value']['grant_type'];
|
| 306 |
if ($module = _aclfield_map_grant_to_module($grant)) {
|
| 307 |
//dprint_r($module);
|
| 308 |
if (!$element['#multiple']) {
|
| 309 |
$perms = array();
|
| 310 |
}
|
| 311 |
$perms[] = array (
|
| 312 |
'realm' => $grant,
|
| 313 |
'grants' => 1, // todo sync to default? or is view always good?
|
| 314 |
'module' => $module,
|
| 315 |
'data' => NULL,
|
| 316 |
);
|
| 317 |
}
|
| 318 |
|
| 319 |
// uncollapse the form
|
| 320 |
$element['#collapsed'] = false;
|
| 321 |
}
|
| 322 |
|
| 323 |
if (count($perms) < 1) {
|
| 324 |
$perms[] = array(
|
| 325 |
'realm' => 'user_all',
|
| 326 |
'grants' => 1,
|
| 327 |
'module' => 'user',
|
| 328 |
'data' => NULL,
|
| 329 |
);
|
| 330 |
}
|
| 331 |
foreach($perms as $key => $perm) {
|
| 332 |
|
| 333 |
// include some hidden information to retrieve on load
|
| 334 |
$element['perms'][$key]['name'] = array (
|
| 335 |
'#type' => 'hidden',
|
| 336 |
'#value' => $grant_types[$perm['module']][$perm['realm']],
|
| 337 |
);
|
| 338 |
$element['perms'][$key]['realm'] = array (
|
| 339 |
'#type' => 'hidden',
|
| 340 |
'#value' => $perm['realm'],
|
| 341 |
);
|
| 342 |
|
| 343 |
$module_form = call_user_func($perm['module'] . '_grant_form', $perm['realm'], $perm['data']);
|
| 344 |
$element['perms'][$key]['data'] = $module_form;
|
| 345 |
$element['perms'][$key]['data']['#parents'] = array_merge($element['#parents'], array('perms', $key, 'data'));
|
| 346 |
|
| 347 |
|
| 348 |
$element['perms'][$key]['grants'] = array (
|
| 349 |
'#type' => 'select',
|
| 350 |
'#title' => t('may'),
|
| 351 |
'#options' => array (
|
| 352 |
0 => t('No permissions'),
|
| 353 |
1 => t('View'),
|
| 354 |
2 => t('View & Edit'),
|
| 355 |
3 => t('View, Edit & Delete'),
|
| 356 |
),
|
| 357 |
'#default_value' => $perm['grants'],
|
| 358 |
);
|
| 359 |
|
| 360 |
$element['perms'][$key]['delete'] = array(
|
| 361 |
'#type' => 'button',
|
| 362 |
'#value' => t('Remove'),
|
| 363 |
'#name' => $element['#name'] . '[perms][' . $key . '][delete]',
|
| 364 |
'#parents' => array_merge($element['#parents'], array('perms', $key, 'delete'))
|
| 365 |
);
|
| 366 |
|
| 367 |
if (!$element['#multiple']) { break; } // safety check - single fields only get 1 item
|
| 368 |
}
|
| 369 |
|
| 370 |
if (count($element['perms']) == 1) { // show the implied grant
|
| 371 |
//dprint_r($element['perms']);
|
| 372 |
$key = array_pop(array_keys($element['perms']));
|
| 373 |
// give at least 1 grant, no no perms is not an option
|
| 374 |
unset($element['perms'][$key]['grants']['#options'][0]);
|
| 375 |
// change the value to 1 if it was something else
|
| 376 |
$element['perms'][$key]['grants']['#value'] = 1;
|
| 377 |
|
| 378 |
// remove the delete option
|
| 379 |
$element['perms'][$key]['delete'] = array (
|
| 380 |
'#type' => 'markup',
|
| 381 |
'#value' => t('You may not delete this entry until you add another.')
|
| 382 |
);
|
| 383 |
}
|
| 384 |
|
| 385 |
$element['perms']['#theme'] = 'aclfield_permissions';
|
| 386 |
|
| 387 |
/* This part of the form adds a new access perm to the list */
|
| 388 |
if ($element['#multiple']) {
|
| 389 |
$select_text = t('Select a new grant priviledge');
|
| 390 |
} else {
|
| 391 |
$select_text = t('Change the grant privilege on this content to');
|
| 392 |
}
|
| 393 |
$element['grant_type'] = array (
|
| 394 |
'#type' => 'select',
|
| 395 |
'#title' => $select_text,
|
| 396 |
'#options' => $grant_types,
|
| 397 |
'#description' => t('Choose a new privilege to add to this content\'s access control list.'),
|
| 398 |
//'#name' => $element['#name'] . '[grant_type]',
|
| 399 |
);
|
| 400 |
|
| 401 |
$element['add_button'] = array(
|
| 402 |
'#type' => 'button',
|
| 403 |
'#value' => t('Add'),
|
| 404 |
'#name' => $element['#name'] . '[add_button]',
|
| 405 |
|
| 406 |
);
|
| 407 |
|
| 408 |
return $element;
|
| 409 |
|
| 410 |
}
|
| 411 |
|
| 412 |
|
| 413 |
/*** Node Access Components ***/
|
| 414 |
|
| 415 |
function aclfield_node_access_records($node) {
|
| 416 |
$acl_fields = _aclfield_acls_in_node($node);
|
| 417 |
$grants = array();
|
| 418 |
|
| 419 |
foreach($acl_fields as $field) {
|
| 420 |
$instance = $node->$field['field_name'];
|
| 421 |
foreach($instance as $item) {
|
| 422 |
$gids = call_user_func($item['module'] . '_grant_process_node', $item['realm'], $node, unserialize($item['data']));
|
| 423 |
if(is_array($gids)) {
|
| 424 |
foreach($gids as $gid) {
|
| 425 |
// check if this nid-realm-gid triple is already set
|
| 426 |
// TODO how to handle multiple fields and grants?
|
| 427 |
if ($grants[$node->nid . '-' . $item['realm'] . '-' . $gid]) {
|
| 428 |
// give the user the max perm possible
|
| 429 |
$old_grant_level = 0;
|
| 430 |
$old_grant_level .= $grants[$node->nid . '-' . $item['realm'] . '-' . $gid]['grant_view'] +
|
| 431 |
$grants[$node->nid . '-' . $item['realm'] . '-' . $gid]['grant_update'] +
|
| 432 |
$grants[$node->nid . '-' . $item['realm'] . '-' . $gid]['grant_delete'];
|
| 433 |
|
| 434 |
// YUCK! i do not like this
|
| 435 |
$items['grants'] = max($items['grants'], $old_grant_level);
|
| 436 |
}
|
| 437 |
|
| 438 |
$grants[$node->nid . '-' . $item['realm'] . '-' . $gid] = array(
|
| 439 |
'realm' => $item['realm'],
|
| 440 |
'gid' => $gid,
|
| 441 |
// TODO turn these into DEFINED values
|
| 442 |
'grant_view' => $item['grants'] > 0,
|
| 443 |
'grant_update' => $item['grants'] > 1,
|
| 444 |
'grant_delete' => $item['grants'] > 2,
|
| 445 |
'priority' => 0,
|
| 446 |
);
|
| 447 |
}
|
| 448 |
}
|
| 449 |
}
|
| 450 |
}
|
| 451 |
//dprint_r("About to return grants");
|
| 452 |
//dprint_r($grants);
|
| 453 |
|
| 454 |
// TODO need to uniquify the grants such that if two grants have the same nid-realm-gid id, they don't clash.
|
| 455 |
|
| 456 |
return $grants;
|
| 457 |
}
|
| 458 |
|
| 459 |
function _aclfield_acls_in_node(&$node) {
|
| 460 |
$content_fields = content_fields();
|
| 461 |
$accum = array();
|
| 462 |
foreach($content_fields as $name => $field) {
|
| 463 |
if ($field['type'] == 'aclfield' && array_key_exists($field['field_name'], $node)) {
|
| 464 |
$accum[] = $field;
|
| 465 |
}
|
| 466 |
}
|
| 467 |
|
| 468 |
return $accum;
|
| 469 |
}
|
| 470 |
|
| 471 |
/*** Theming ***/
|
| 472 |
|
| 473 |
function theme_aclfield_permissions($form) {
|
| 474 |
|
| 475 |
$kids = element_children($form);
|
| 476 |
if (count($kids) < 1) {
|
| 477 |
return drupal_render($form);
|
| 478 |
}
|
| 479 |
// start setting up a table of perms
|
| 480 |
$rows = array();
|
| 481 |
foreach($kids as $kid) {
|
| 482 |
// remove the titles from the check boxes so they don't get put in each cell
|
| 483 |
|
| 484 |
$column_1 = '<h5 class = "aclfield-permission-title">' . $form[$kid]['name']['#value'] . '</h5>';
|
| 485 |
$column_1 .= drupal_render($form[$kid]['data']);
|
| 486 |
|
| 487 |
$column_2 = $form[$kid]['grants']['#title'];
|
| 488 |
unset($form[$kid]['grants']['#title']);
|
| 489 |
|
| 490 |
$rows[] = array (
|
| 491 |
$column_1,
|
| 492 |
$column_2,
|
| 493 |
drupal_render($form[$kid]['grants']),
|
| 494 |
drupal_render($form[$kid]['delete'])
|
| 495 |
);
|
| 496 |
}
|
| 497 |
|
| 498 |
// no header needed, really
|
| 499 |
$output = theme('table', NULL, $rows);
|
| 500 |
|
| 501 |
$output .= drupal_render($form);
|
| 502 |
return $output;
|
| 503 |
}
|
| 504 |
|
| 505 |
function theme_aclfield($element) {
|
| 506 |
unset($element['#value']);
|
| 507 |
return theme('fieldset', $element);
|
| 508 |
}
|
| 509 |
|
| 510 |
/***** Some Helper Functions *****/
|
| 511 |
|
| 512 |
function aclfield_array_to_text($list, $join = 'and') {
|
| 513 |
switch(count($list)) {
|
| 514 |
case 0:
|
| 515 |
return t('none');
|
| 516 |
case 1:
|
| 517 |
return array_pop($list);
|
| 518 |
default:
|
| 519 |
$last = array_pop($list);
|
| 520 |
$rest = implode(', ', $list);
|
| 521 |
if ($join == 'and') {
|
| 522 |
return $rest . ' '. t('and') . " $last";
|
| 523 |
} else {
|
| 524 |
return $rest . ' '. t('or') . " $last";
|
| 525 |
}
|
| 526 |
}
|
| 527 |
}
|
| 528 |
|
| 529 |
// include hooks for other modules
|
| 530 |
// these should be submitted to module maintainers and incorporated
|
| 531 |
// into the appropriate module code.
|
| 532 |
|
| 533 |
require_once dirname(__FILE__) . '/aclfield.inc';
|