| 1 |
<?php
|
| 2 |
// $Id: nodeperm_role.module,v 1.9 2006/06/09 15:06:05 robb Exp $
|
| 3 |
// Original implementation by Jonathan Chaffer.
|
| 4 |
// Current maintainer Robb Canfield from 2005/12/01 forward
|
| 5 |
|
| 6 |
|
| 7 |
/**
|
| 8 |
* History
|
| 9 |
* Date: 2006-06-10
|
| 10 |
* - Changed matrix theme delimiter to a constant and added documentation
|
| 11 |
* - Settings code rewritten to use hook_menu. hook_settings cannot handle custom submit code
|
| 12 |
*
|
| 13 |
* Date: 2006-06-10
|
| 14 |
* - Workflow support added but not fully tested
|
| 15 |
* - Added new form elements for creating a matrix
|
| 16 |
*
|
| 17 |
* Date: 2006-06-08
|
| 18 |
* - ALPHA quality code
|
| 19 |
* - Converted code to 4.7 (forms API)
|
| 20 |
* - Depreciated code (search for /^#/) that handled restoring states - 4.7 does this proprerly now
|
| 21 |
* - Scheduled for depreciation the option to only handle edit operations
|
| 22 |
* - Handleing view operations add no appreciable processing overhead. The code to show/hide view permissions
|
| 23 |
* is a complication that is uneeded.
|
| 24 |
* - Modified some form descriptions to make things clearer
|
| 25 |
* - Workflow Action method has been disabled until a future date
|
| 26 |
* -- action_node_assign_role_perms() --> XXX_action_node_assign_role_perms()
|
| 27 |
*/
|
| 28 |
|
| 29 |
/**
|
| 30 |
* Implementation of hook_help().
|
| 31 |
*/
|
| 32 |
function nodeperm_role_help($section) {
|
| 33 |
switch ($section) {
|
| 34 |
case 'admin/modules#description':
|
| 35 |
return t('Grants access to nodes based on users\' roles. After enabling/disabling this module you must go to <a href="%nodeperm_role_settings_url">admin/settings/nodeperm_role</a>.', array('%nodeperm_role_settings_url' => url('admin/settings/nodeperm_role')));
|
| 36 |
case 'admin/settings/nodeperm_role':
|
| 37 |
return t('It is strongly recommended that you back up your database before changing these options! Specifically, your node_access table will be altered.');
|
| 38 |
}
|
| 39 |
}
|
| 40 |
|
| 41 |
/**
|
| 42 |
* Implementation of hook_menu().
|
| 43 |
*/
|
| 44 |
function nodeperm_role_menu($may_cache) {
|
| 45 |
$items = array();
|
| 46 |
|
| 47 |
if (! $may_cache) {
|
| 48 |
$items[] = array(
|
| 49 |
'path' => 'admin/settings/nodeperm_role',
|
| 50 |
'title' => 'Nodeperm Role Settings',
|
| 51 |
'access' => user_access('administer nodes'),
|
| 52 |
'callback' => 'nodeperm_role_configure',
|
| 53 |
);
|
| 54 |
}
|
| 55 |
|
| 56 |
return $items;
|
| 57 |
}
|
| 58 |
|
| 59 |
/**
|
| 60 |
* Implementation of hook_perm().
|
| 61 |
*/
|
| 62 |
function nodeperm_role_perm() {
|
| 63 |
return array('edit own node permissions');
|
| 64 |
}
|
| 65 |
|
| 66 |
/**
|
| 67 |
* Implementation of hook_node_grants().
|
| 68 |
*
|
| 69 |
* Since we are restricting access based on user role, all we have to do is return
|
| 70 |
* the user's role IDs.
|
| 71 |
*/
|
| 72 |
function nodeperm_role_node_grants($user, $op) {
|
| 73 |
return array('nodeperm_role' => array_keys($user->roles));
|
| 74 |
}
|
| 75 |
|
| 76 |
/**
|
| 77 |
* Triggered by hook_menu code. Cannot use normal hook_settings since this form is a tad more complex than supported by hook_settings
|
| 78 |
*/
|
| 79 |
function nodeperm_role_configure() {
|
| 80 |
$form = array();
|
| 81 |
|
| 82 |
drupal_set_title(t('Role-based Node Permissions'));
|
| 83 |
|
| 84 |
$status = t('disabled');
|
| 85 |
$btn_text = t('Enable');
|
| 86 |
if (variable_get('nodeperm_role_enabled', 1)) {
|
| 87 |
$status = t('enabled');
|
| 88 |
$btn_text = t('Disable');
|
| 89 |
}
|
| 90 |
|
| 91 |
$form['module_status'] = array(
|
| 92 |
'#type' => 'fieldset',
|
| 93 |
'#title' => t('Module status'),
|
| 94 |
'#collapsible' => FALSE,
|
| 95 |
'#collapsed' => FALSE,
|
| 96 |
);
|
| 97 |
|
| 98 |
$form['module_status']['op'] = array(
|
| 99 |
'#type' => 'submit',
|
| 100 |
'#value' => $btn_text,
|
| 101 |
'#description' => t('Role-based node permissions are currently') . ' ' . $status . '.<br />',
|
| 102 |
'#title' => t('Module status'),
|
| 103 |
);
|
| 104 |
|
| 105 |
$form['module_status']['cleanup'] = array(
|
| 106 |
'#type' => 'submit',
|
| 107 |
'#value' => 'Repair',
|
| 108 |
'#description' => t('Sometimes the node_access table can get out of sync. Click on this button to synchronize the node_access table.'),
|
| 109 |
'#title' => t('Cleanup node access table'),
|
| 110 |
);
|
| 111 |
|
| 112 |
|
| 113 |
$form['permissions_model'] = array(
|
| 114 |
'#type' => 'fieldset',
|
| 115 |
'#title' => t('Permissions model'),
|
| 116 |
'#description' => '',
|
| 117 |
'#collapsible' => FALSE,
|
| 118 |
'#collapsed' => FALSE,
|
| 119 |
);
|
| 120 |
|
| 121 |
$form['permissions_model']['nodeperm_role_mode'] = array(
|
| 122 |
'#type' => 'radios',
|
| 123 |
'#title' => t('Operations to control'),
|
| 124 |
'#default_value' => variable_get('nodeperm_role_mode', 0), # 0 = view and edit, 1 = edit only
|
| 125 |
'#options' => array(
|
| 126 |
'0' => t('Allow both view and edit based permissions to be controlled.'),
|
| 127 |
'1' => t('Only allow edit based permissions to be adjusted (view will use Drupal default). <b>WARNING: Scheduled for depreciation</b>.'),
|
| 128 |
),
|
| 129 |
);
|
| 130 |
|
| 131 |
// This section is DEPRECIATED
|
| 132 |
// - 4.7 appears to preserve flags and author when content is edited (YEA)
|
| 133 |
// - It has been converted to 4.7 forms control as an excersie (and works).
|
| 134 |
# // Create the node options preservation table.
|
| 135 |
# $form['editing_options'] = array(
|
| 136 |
# '#type' => 'fieldset',
|
| 137 |
# '#title' => t('Editing options'),
|
| 138 |
# '#description' => t('<div id="help">Drupal\'s default behavior is to reset options to their <a href="%default-options-link">defaults</a> when a node is edited. Change this behavior by indicating which options will be preserved when a user edits a node they don\'t own. Users with "administer nodes" permission may override these settings within the node form. </div>', array('%default-options-link' => url('admin/node/configure/types'))),
|
| 139 |
# '#collapsible' => FALSE,
|
| 140 |
# '#collapsed' => FALSE,
|
| 141 |
# );
|
| 142 |
|
| 143 |
# // The various flags used by nodes
|
| 144 |
# $edit_settings = array('publish' => 'node_status_edit_', 'promote' => 'node_promote_edit_', 'moderate' => 'node_moderate_edit_', 'sticky' => 'node_sticky_edit_');
|
| 145 |
# $form['editing_options']['matrix'] = array(
|
| 146 |
# '#type' => 'crg_formtable',
|
| 147 |
# '#header' => array_merge(array(t('type')), array_keys($edit_settings)),
|
| 148 |
# );
|
| 149 |
# foreach (node_get_types() as $type => $name) {
|
| 150 |
# $cols = array();
|
| 151 |
|
| 152 |
# // Build a fake node so the mdoule associated witht he node can return the node's title
|
| 153 |
# $node = new StdClass();
|
| 154 |
# $node->type = $type;
|
| 155 |
|
| 156 |
# // Set row title to module name
|
| 157 |
# $cols[] = array(
|
| 158 |
# '#value' => node_get_name($node),
|
| 159 |
# );
|
| 160 |
|
| 161 |
# foreach ($edit_settings as $option => $varname) {
|
| 162 |
# $varname = $varname. $type;
|
| 163 |
# $cols[] = array(
|
| 164 |
# '#name' => $varname,
|
| 165 |
# '#type' => 'checkbox',
|
| 166 |
# '#title' => '',
|
| 167 |
# '#default_value' => 1,
|
| 168 |
# '#required' => FALSE,
|
| 169 |
# '#return_value' => (variable_get($varname, 0)) ? TRUE : FALSE,
|
| 170 |
# '#attributes' => array(
|
| 171 |
# 'align' => 'center',
|
| 172 |
# 'width' => 55,
|
| 173 |
# ),
|
| 174 |
# );
|
| 175 |
# }
|
| 176 |
|
| 177 |
# $form['editing_options']['matrix']['#rows'][] = $cols;
|
| 178 |
# }
|
| 179 |
|
| 180 |
|
| 181 |
// CRG : Add an advanced option that will allow for incremental changes to access instead of replacement changes
|
| 182 |
$form['advanced'] = array(
|
| 183 |
'#type' => 'fieldset',
|
| 184 |
'#title' => t('Advanced options'),
|
| 185 |
'#collapsible' => FALSE,
|
| 186 |
'#collapsed' => FALSE,
|
| 187 |
);
|
| 188 |
$form['advanced']['nodeperm_role_inherit'] = array(
|
| 189 |
'#type' => 'checkbox',
|
| 190 |
'#title' => t("Allow incremental per-role settings"),
|
| 191 |
'#description' => t('Allow worflow actions to inherit, remove or set permissions for roles. Inheriting means any existing permissions for this role are retained by this action. Only applicable if the Workflow module is loaded.'),
|
| 192 |
'#default_value'=> variable_get('nodeperm_role_inherit', 0),
|
| 193 |
);
|
| 194 |
|
| 195 |
$form['op'] = array(
|
| 196 |
'#type' => 'submit',
|
| 197 |
'#value' => t('Save changes'),
|
| 198 |
);
|
| 199 |
|
| 200 |
# Call form buidler
|
| 201 |
return drupal_get_form('nodeperm_role_config', $form);
|
| 202 |
}
|
| 203 |
|
| 204 |
/**
|
| 205 |
* Implementation of hook_form_submit()
|
| 206 |
*/
|
| 207 |
function nodeperm_role_config_submit($form_id, $form_values) {
|
| 208 |
|
| 209 |
$queries = array();
|
| 210 |
$op = $_POST['op'];
|
| 211 |
|
| 212 |
# Activate/deactive and clean up node_access table
|
| 213 |
if ($op == t('Enable')) {
|
| 214 |
variable_set('nodeperm_role_enabled', 1);
|
| 215 |
$queries[] = 'DELETE FROM {node_access} WHERE nid = 0 AND realm = "all"';
|
| 216 |
drupal_set_message(t('Role-based node permissions are now enabled'));
|
| 217 |
}
|
| 218 |
elseif ($op == t('Disable')) {
|
| 219 |
variable_set('nodeperm_role_enabled', 0);
|
| 220 |
$queries[] = 'DELETE FROM {node_access} WHERE nid = 0 AND realm = "all"';
|
| 221 |
$queries[] = 'INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (0, 0, "all", 1, 0, 0)';
|
| 222 |
drupal_set_message(t('Role-based node permissions are now disabled'));
|
| 223 |
}
|
| 224 |
|
| 225 |
# Always syncronize node_access table (repair)
|
| 226 |
if (variable_get('nodeperm_role_mode', 0) == 0) { // Edit perms only
|
| 227 |
if (db_result(db_query('SELECT COUNT(nid) FROM {node_access} WHERE realm = "all" AND nid = 0'))) {
|
| 228 |
$queries[] = 'DELETE FROM {node_access} WHERE nid = 0 AND realm = "all"';
|
| 229 |
}
|
| 230 |
}
|
| 231 |
else { // View and edit perms
|
| 232 |
if (db_result(db_query('SELECT COUNT(nid) FROM {node_access} WHERE nid = 0 AND realm = "all" AND grant_view = 1')) == 0) {
|
| 233 |
$queries[] = 'DELETE FROM {node_access} WHERE nid = 0 AND realm = "all"';
|
| 234 |
$queries[] = 'INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (0, 0, "all", 1, 0, 0)';
|
| 235 |
}
|
| 236 |
if (db_result(db_query('SELECT COUNT(nid) FROM {node_access} WHERE realm = "nodeperm_role" AND grant_update = 0'))) {
|
| 237 |
$queries[] = 'DELETE FROM {node_access} WHERE realm = "nodeperm_role" AND grant_update = 0';
|
| 238 |
}
|
| 239 |
}
|
| 240 |
|
| 241 |
// Save variables - form was NOT a tree so do not use hierarchial names
|
| 242 |
variable_set('nodeperm_role_inherit', $form_values['nodeperm_role_inherit']);
|
| 243 |
variable_set('nodeperm_role_mode', $form_values['nodeperm_role_mode']);
|
| 244 |
|
| 245 |
$queries = array_unique($queries);
|
| 246 |
if (count($queries)) {
|
| 247 |
drupal_set_message(t('The following queries were run to optimize the node_access table:'));
|
| 248 |
foreach ($queries as $query) {
|
| 249 |
db_query($query);
|
| 250 |
drupal_set_message($query);
|
| 251 |
}
|
| 252 |
} else
|
| 253 |
{
|
| 254 |
drupal_set_message("No problems found in SQL tables");
|
| 255 |
}
|
| 256 |
}
|
| 257 |
|
| 258 |
|
| 259 |
/**
|
| 260 |
* Implemention of hook_form_alter
|
| 261 |
*
|
| 262 |
* A role view/edit section is added to the existing form (in its own section) if the form passed
|
| 263 |
* is node related.
|
| 264 |
*
|
| 265 |
*/
|
| 266 |
|
| 267 |
function nodeperm_role_form_alter($form_id, &$form) {
|
| 268 |
// Only intercept NODE related forms
|
| 269 |
if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
|
| 270 |
// Allow node authors and admins to set view/edit permissions.
|
| 271 |
if (user_access('edit own node permissions') || user_access('administer nodes')) {
|
| 272 |
$node = $form['#node']; // Gain easy access to node data
|
| 273 |
|
| 274 |
if (!isset($node->nodeperm_role_view)) {
|
| 275 |
$edit = $_POST['edit'];
|
| 276 |
// Load the node access grants from the database.
|
| 277 |
$node->nodeperm_role_view = nodeperm_role_load_view($node->nid);
|
| 278 |
$node->nodeperm_role_edit = nodeperm_role_load_edit($node->nid);
|
| 279 |
}
|
| 280 |
$roles = user_roles();
|
| 281 |
$roles[-1] = t('none');
|
| 282 |
ksort($roles);
|
| 283 |
|
| 284 |
// Create a new section for ndoeperm roles
|
| 285 |
// - Note : The only way to control WHERE this form section appears is to intercept the theme code for a node. For this case that is not worth the effort
|
| 286 |
$form['role_permissions'] = array(
|
| 287 |
'#type' => 'fieldset',
|
| 288 |
'#title' => 'Additonal role permissions',
|
| 289 |
'#description' => 'Allow other roles to access this node',
|
| 290 |
'#collapsible' => 1,
|
| 291 |
'#collapsed' => 1,
|
| 292 |
);
|
| 293 |
|
| 294 |
if (variable_get('nodeperm_role_mode', 0) == 0) {
|
| 295 |
// If view is being controlled by nodeperm_role then display view selection
|
| 296 |
$form['role_permissions']['nodeperm_role_view'] = array(
|
| 297 |
'#title' => t('Roles that can view'),
|
| 298 |
'#default_value' => ($edit['nodeperm_role_view']) ? $edit['nodeperm_role_view'] : $node->nodeperm_role_view,
|
| 299 |
'#type' => 'select',
|
| 300 |
'#options' => $roles,
|
| 301 |
'#description' => t('Select other roles that may view your post.'),
|
| 302 |
'#multiple' => True,
|
| 303 |
'#rows' => 5,
|
| 304 |
);
|
| 305 |
}
|
| 306 |
// Edit is always under the control of nodeperm_role
|
| 307 |
$form['role_permissions']['nodeperm_role_edit'] = array(
|
| 308 |
'#title' => t('Roles that can edit'),
|
| 309 |
'#default_value' => ($edit['nodeperm_role_edit']) ? $edit['nodeperm_role_edit'] : $node->nodeperm_role_edit,
|
| 310 |
'#type' => 'select',
|
| 311 |
'#options' => $roles,
|
| 312 |
'#description' => t('Select other roles that may edit your post.'),
|
| 313 |
'#multiple' => True,
|
| 314 |
'#rows' => 5,
|
| 315 |
);
|
| 316 |
|
| 317 |
}
|
| 318 |
}
|
| 319 |
|
| 320 |
# No return value needed, the passed $form array is modified in-place as apropriate
|
| 321 |
}
|
| 322 |
|
| 323 |
/**
|
| 324 |
* Implementation of hook_nodeapi().
|
| 325 |
*
|
| 326 |
*/
|
| 327 |
function nodeperm_role_nodeapi(&$node, $op, $arg = 0) {
|
| 328 |
switch ($op) {
|
| 329 |
|
| 330 |
// DEPRECIATED - anomly referenced below has been fixed in 4.7
|
| 331 |
# case 'validate':
|
| 332 |
# global $user;
|
| 333 |
# // A batch of special cases for group members who are editing a node, not
|
| 334 |
# // the original author, and don't have administer nodes permission.
|
| 335 |
# if ($_POST['op'] == t('Submit') && $node->nid && !user_access('administer nodes')) {
|
| 336 |
# // Drupal has this nasty bug that assumes the editor is also the author
|
| 337 |
# // and assigns complete authorship to the editor. See here for more details:
|
| 338 |
# // http://drupal.org/node/11071
|
| 339 |
# $old_node = db_fetch_object(db_query('SELECT uid, status, promote, sticky, moderate FROM {node} WHERE nid = %d', $node->nid));
|
| 340 |
|
| 341 |
# if ($node->uid != $old_node->uid) {
|
| 342 |
# $node->uid = $old_node->uid;
|
| 343 |
# // Determine if the settings are preserved during group editing. There
|
| 344 |
# // is also a patch for this here: http://drupal.org/node/7940
|
| 345 |
# $node->status = (variable_get("node_status_edit_$node->type", 1)) ? $old_node->status : variable_get("node_status_$node->type", 1);
|
| 346 |
# $node->promote = (variable_get("node_promote_edit_$node->type", 1)) ? $old_node->promote : variable_get("node_promote_$node->type", 1);
|
| 347 |
# $node->sticky = (variable_get("node_sticky_edit_$node->type", 1)) ? $old_node->sticky : variable_get("node_sticky_$node->type", 0);
|
| 348 |
# $node->moderate = (variable_get("node_moderate_edit_$node->type", 1)) ? $old_node->moderate : variable_get("node_moderate_$node->type", 0);
|
| 349 |
# }
|
| 350 |
# }
|
| 351 |
# break;
|
| 352 |
|
| 353 |
|
| 354 |
case 'delete':
|
| 355 |
// When a node is deleted, delete any relevant grants.
|
| 356 |
db_query("DELETE FROM {node_access} WHERE nid = %d AND realm = 'nodeperm_role'", $node->nid);
|
| 357 |
break;
|
| 358 |
|
| 359 |
case 'insert':
|
| 360 |
case 'update':
|
| 361 |
if ((user_access('edit own node permissions') || user_access('administer nodes')) && ($node->nodeperm_role_view || $node->nodeperm_role_edit)) {
|
| 362 |
nodeperm_save($node);
|
| 363 |
}
|
| 364 |
break;
|
| 365 |
}
|
| 366 |
}
|
| 367 |
|
| 368 |
/**
|
| 369 |
* Save node permissions by writing them to the database.
|
| 370 |
*/
|
| 371 |
function nodeperm_save($node) {
|
| 372 |
// If either of the special array are set then update node_access.
|
| 373 |
if (is_array($node->nodeperm_role_view) || is_array($node->nodeperm_role_edit)) {
|
| 374 |
$roles = nodeperm_get_roles();
|
| 375 |
db_query("DELETE FROM {node_access} WHERE nid = %d AND realm = 'nodeperm_role'", $node->nid);
|
| 376 |
$grants = array();
|
| 377 |
|
| 378 |
// Get current node permissions
|
| 379 |
$node->nodeperm_role_view = isset($node->nodeperm_role_view) ? $node->nodeperm_role_view : array();
|
| 380 |
$node->nodeperm_role_edit = isset($node->nodeperm_role_edit) ? $node->nodeperm_role_edit : array();
|
| 381 |
|
| 382 |
/* A rid of -1 means that the user doesn't want anybody else to edit their entry. */
|
| 383 |
foreach ($node->nodeperm_role_view as $rid) {
|
| 384 |
if ($rid != -1) {
|
| 385 |
$grants[$rid]['view'] = 1;
|
| 386 |
}
|
| 387 |
}
|
| 388 |
foreach ($node->nodeperm_role_edit as $rid) {
|
| 389 |
if ($rid != -1) {
|
| 390 |
$grants[$rid]['view'] = 1;
|
| 391 |
$grants[$rid]['edit'] = 1;
|
| 392 |
}
|
| 393 |
}
|
| 394 |
|
| 395 |
foreach ($grants as $rid => $grant) {
|
| 396 |
// Optimize the node access table. If all permissions are FALSE then do not bother adding it (deny is the default)
|
| 397 |
// - In theory reducing node access rows will improve performance
|
| 398 |
// - Note that edit permission is also used for delete permission
|
| 399 |
if ($grant['view'] || $grant['edit']) {
|
| 400 |
// %d format control will auto-convert undefined values to 0 which is what is desired
|
| 401 |
db_query('INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, \'nodeperm_role\', %d, %d, %d)', $node->nid, $rid, $grant['view'], $grant['edit'], $grant['edit']);
|
| 402 |
if ($grant['view']) {
|
| 403 |
$rnames_view[] = $roles[$rid];
|
| 404 |
}
|
| 405 |
if ($grant['edit']) {
|
| 406 |
$rnames_edit[] = $roles[$rid];
|
| 407 |
}
|
| 408 |
}
|
| 409 |
}
|
| 410 |
if ($rnames_view) {
|
| 411 |
watchdog('action', t('Changed role view permissions of node %nid to <em>%rnames</em>', array('%nid' => $node->nid, '%rnames' => implode(', ', $rnames_view))));
|
| 412 |
}
|
| 413 |
if ($rnames_edit) {
|
| 414 |
watchdog('action', t('Changed role edit permissions of node %nid to <em>%rnames</em>', array('%nid' => $node->nid, '%rnames' => implode(', ', $rnames_edit))));
|
| 415 |
}
|
| 416 |
}
|
| 417 |
}
|
| 418 |
|
| 419 |
|
| 420 |
function nodeperm_role_load_view($nid) {
|
| 421 |
$result = db_query("SELECT na.gid FROM {node_access} na WHERE na.nid = %d AND na.realm = 'nodeperm_role' AND na.grant_view = 1", $nid);
|
| 422 |
$nodeperm_role_view = array();
|
| 423 |
while ($grant = db_fetch_object($result)) {
|
| 424 |
$nodeperm_role_view[] = $grant->gid;
|
| 425 |
}
|
| 426 |
|
| 427 |
return $nodeperm_role_view;
|
| 428 |
}
|
| 429 |
|
| 430 |
function nodeperm_role_load_edit($nid) {
|
| 431 |
$result = db_query("SELECT na.gid FROM {node_access} na WHERE na.nid = %d AND na.realm = 'nodeperm_role' AND na.grant_update = 1", $nid);
|
| 432 |
$nodeperm_role_edit = array();
|
| 433 |
while ($grant = db_fetch_object($result)) {
|
| 434 |
$nodeperm_role_edit[] = $grant->gid;
|
| 435 |
}
|
| 436 |
|
| 437 |
return $nodeperm_role_edit;
|
| 438 |
}
|
| 439 |
|
| 440 |
function nodeperm_get_roles($none = False) {
|
| 441 |
static $roles;
|
| 442 |
|
| 443 |
if (!$roles) {
|
| 444 |
$result = db_query("SELECT rid, name FROM {role} ORDER BY name");
|
| 445 |
while ($data = db_fetch_object($result)) {
|
| 446 |
$roles[$data->rid] = $data->name;
|
| 447 |
}
|
| 448 |
}
|
| 449 |
|
| 450 |
return $roles;
|
| 451 |
}
|
| 452 |
|
| 453 |
/**
|
| 454 |
* Implementation of a Drupal action.
|
| 455 |
* Alters node level role permissions for a node.
|
| 456 |
*
|
| 457 |
* If advanced inheritence flag is set then this action supports set/remove/inherit logic. This is the only
|
| 458 |
* place such code exists. Inheritence does not make any sense when a node is being edited as there is nothing
|
| 459 |
* to inherit from!
|
| 460 |
*/
|
| 461 |
function action_node_assign_role_perms($op, &$edit, &$node) {
|
| 462 |
// Set some common values, used by many operations
|
| 463 |
|
| 464 |
// *** From module settings
|
| 465 |
$advanced_form_name = 'nodeperm_role_inherit';
|
| 466 |
$is_advanced_form = variable_get($advanced_form_name, 0);
|
| 467 |
// True if view permission is being maintained
|
| 468 |
$is_view = variable_get('nodeperm_role_mode', 0) == 0;
|
| 469 |
|
| 470 |
|
| 471 |
// Handle operation
|
| 472 |
switch($op) {
|
| 473 |
case 'metadata':
|
| 474 |
return array(
|
| 475 |
'description' => t('Change role-based permissions'),
|
| 476 |
'type' => t('Node'),
|
| 477 |
'batchable' => false,
|
| 478 |
'configurable' => true,
|
| 479 |
);
|
| 480 |
break;
|
| 481 |
|
| 482 |
case 'do':
|
| 483 |
// If node contains nodeperm settings use them, otherwise fetch them
|
| 484 |
// - In most cases the data will not exist, but allow for it in case others have done some modifications
|
| 485 |
// - Only bother getting values if advanced flag is set
|
| 486 |
// - Make sure defaults are initialized to arrays
|
| 487 |
// - When done each element contains the role IDs currently granted the specific permission
|
| 488 |
$default['view'] = array();
|
| 489 |
$default['edit'] = array();
|
| 490 |
|
| 491 |
if ($is_advanced_form) {
|
| 492 |
// Avoid grabbing view permissions if this has been deactivated
|
| 493 |
if ($is_view) {
|
| 494 |
if (is_array($node->nodeperm_role_view)) {
|
| 495 |
$default['view'] = $node->nodeperm_role_view;
|
| 496 |
}
|
| 497 |
else {
|
| 498 |
$default['view'] = nodeperm_role_load_view($node->nid);
|
| 499 |
}
|
| 500 |
}
|
| 501 |
// edit (aka update) (also used for delete) is always grabbed
|
| 502 |
if (is_array($node->nodeperm_role_edit)) {
|
| 503 |
$default['edit'] = $node->nodeperm_role_edit;
|
| 504 |
}
|
| 505 |
else {
|
| 506 |
$default['edit'] = nodeperm_role_load_view($node->nid);
|
| 507 |
}
|
| 508 |
} // End of inheritence support check
|
| 509 |
|
| 510 |
// Loop through parameters passed via edit[]
|
| 511 |
// Handle new style inheritance settings in a way that is compatible with the old
|
| 512 |
// - A teeny bit slower then original code but of little consequence for actions as they fire rarely (as compared to reads) in all but VERY extreme cases
|
| 513 |
$grants = array('view' => array(), 'edit' => array()); // Initialize final result set
|
| 514 |
|
| 515 |
foreach (nodeperm_get_roles() as $rid => $role_name) {
|
| 516 |
// Loop through each permission
|
| 517 |
foreach (nodeperm_role_perms() as $p) {
|
| 518 |
$set_name = 'nodeperm_role_'. $p; // Name of element holding rids that have explicit permissions
|
| 519 |
$inherit_name = 'nodeperm_role_'. $p . '_inherit'; // Name of element holding rids that inherit
|
| 520 |
|
| 521 |
if (in_array($rid, $edit[$set_name])) {
|
| 522 |
// permissions was explicitly specified for this role
|
| 523 |
$grants[$p][] = $rid;
|
| 524 |
}
|
| 525 |
elseif(in_array($rid, $edit[$inherit_name])) {
|
| 526 |
// permissions is inherited. If this rid is in default set then add it to result set
|
| 527 |
if (in_array($rid, $default[$p])) {
|
| 528 |
$grants[$p][] = $rid;
|
| 529 |
}
|
| 530 |
}
|
| 531 |
// else denial is assumed (does not appear in an array)
|
| 532 |
}
|
| 533 |
}
|
| 534 |
|
| 535 |
// Add back into node and save nodeperm data
|
| 536 |
if (variable_get('nodeperm_role_mode', 0) == 0) {
|
| 537 |
$node->nodeperm_role_view = $grants['view'];
|
| 538 |
}
|
| 539 |
$node->nodeperm_role_edit = $grants['edit'];
|
| 540 |
|
| 541 |
// Data is now in normal nodeperm format:
|
| 542 |
// nodeperm_role_view = array of rids granted view
|
| 543 |
// nodeperm_role_edit = array of rids granted edit
|
| 544 |
nodeperm_save($node);
|
| 545 |
break;
|
| 546 |
|
| 547 |
case 'form':
|
| 548 |
// add form components
|
| 549 |
$roles = nodeperm_get_roles();
|
| 550 |
$roles[-1] = t('none');
|
| 551 |
ksort($roles);
|
| 552 |
$output = '';
|
| 553 |
|
| 554 |
// Get available permisisons, often used as a suffix for array keys
|
| 555 |
$perms = nodeperm_role_perms();
|
| 556 |
|
| 557 |
|
| 558 |
// Make sure elements are initialized
|
| 559 |
foreach (array('', '_inherit') as $type) {
|
| 560 |
foreach ($perms as $p) {
|
| 561 |
$name = 'nodeperm_role_' . $p . $type;
|
| 562 |
if (! $edit[$name]) {
|
| 563 |
$edit[$name] = array();
|
| 564 |
}
|
| 565 |
}
|
| 566 |
}
|
| 567 |
|
| 568 |
// Remember the type of form being generated
|
| 569 |
// - Use a different name so detection between re-display and initial display is possible
|
| 570 |
$form = array();
|
| 571 |
$form[$advanced_form_name] = array(
|
| 572 |
'#type' => 'hidden',
|
| 573 |
'#value' => $is_advanced_form,
|
| 574 |
);
|
| 575 |
|
| 576 |
if ($is_advanced_form) {
|
| 577 |
// Support the advanced permission handling
|
| 578 |
// - There is no preview so this check is probably not needed but do it anyway for completeness
|
| 579 |
if (! array_key_exists('nodeperm_role_form_type', $edit)) {
|
| 580 |
// Convert an array or roles to applicable options
|
| 581 |
// actions saves paramater data in $edit[] (same as CGI which normally makes it easy to transition between the two but doesn't help much now)
|
| 582 |
// - This is NOT blazing fast but for the very few times it will be triggered (defining an action) it is fine
|
| 583 |
foreach ( nodeperm_get_roles() as $rid => $role_name) {
|
| 584 |
// Loop through each permission
|
| 585 |
foreach ($perms as $p) {
|
| 586 |
$cgi_name = 'nodeperm_role_'. $p .'_' . $rid;
|
| 587 |
if (in_array($rid, $edit['nodeperm_role_' . $p])) {
|
| 588 |
$edit[$cgi_name] = '1';
|
| 589 |
}
|
| 590 |
elseif($edit['nodeperm_role_' . $p . '_inherit'] && in_array($rid, $edit['nodeperm_role_' . $p . '_inherit'])) {
|
| 591 |
$edit[$cgi_name] = '-';
|
| 592 |
} else {
|
| 593 |
// Assume denial
|
| 594 |
$edit[$cgi_name] = '0';
|
| 595 |
}
|
| 596 |
}
|
| 597 |
}
|
| 598 |
}
|
| 599 |
|
| 600 |
$form['advanced'] = array(
|
| 601 |
'#type' => 'fieldset',
|
| 602 |
'#title' => t('Advanced role permissions'),
|
| 603 |
'#description' => t("For each role select '-' to retain exsiting permission (no change) or 'Y' to grant permission or 'N' to deny permission"),
|
| 604 |
'#collapsible' => FALSE,
|
| 605 |
'#collapsed' => FALSE,
|
| 606 |
);
|
| 607 |
|
| 608 |
$columns = array();
|
| 609 |
|
| 610 |
$form['advanced']['perms'] = array(
|
| 611 |
'#type' => 'matrix',
|
| 612 |
'#header' => array_merge(array('role'), $perms),
|
| 613 |
);
|
| 614 |
|
| 615 |
|
| 616 |
# Build each row
|
| 617 |
$row_count = 1;
|
| 618 |
foreach ( nodeperm_get_roles() as $rid => $role_name) {
|
| 619 |
$cols = array();
|
| 620 |
|
| 621 |
# Add role name
|
| 622 |
$cols['role'] = array(
|
| 623 |
'#value' => $role_name,
|
| 624 |
);
|
| 625 |
|
| 626 |
// Select field for each permission
|
| 627 |
foreach ($perms as $p) {
|
| 628 |
// Each select field
|
| 629 |
// - Name is manually created since there is no key for this element (using simplfied matrix form)
|
| 630 |
$cgi_name = 'nodeperm_role_'. $p .'_' . $rid;
|
| 631 |
$cols[$cgi_name] = array(
|
| 632 |
'#type' => 'select',
|
| 633 |
'#default_value' => $edit[$cgi_name],
|
| 634 |
'#options' => array(
|
| 635 |
'-' => '-',
|
| 636 |
'0' => t('N'),
|
| 637 |
'1' => t('Y'),
|
| 638 |
),
|
| 639 |
);
|
| 640 |
}
|
| 641 |
|
| 642 |
// Add the new row
|
| 643 |
$form['advanced']['perms']['#rows'][] = $cols;
|
| 644 |
}
|
| 645 |
} else {
|
| 646 |
// Simplified interface
|
| 647 |
if (variable_get('nodeperm_role_mode', 0) == 0) {
|
| 648 |
$form['nodeperm_role_view'] = array(
|
| 649 |
'#type' => 'select',
|
| 650 |
'#title' => t('Roles that may view the node'),
|
| 651 |
'#default_value' => $edit['nodeperm_role_view'],
|
| 652 |
'#description' => t('Select the roles that will be able to view the node.'),
|
| 653 |
'#options' => $roles,
|
| 654 |
);
|
| 655 |
}
|
| 656 |
$form['nodeperm_role_view'] = array(
|
| 657 |
'#type' => 'select',
|
| 658 |
'#title' => t('Roles that may edit the node'),
|
| 659 |
'#default_value' => $edit['nodeperm_role_edit'],
|
| 660 |
'#description' => t('Select the roles that will be able to edit the node.'),
|
| 661 |
'#options' => $roles,
|
| 662 |
);
|
| 663 |
}
|
| 664 |
#drupal_set_message("<pre>". print_r($form,1).'</pre>');
|
| 665 |
return $form;
|
| 666 |
break;
|
| 667 |
|
| 668 |
|
| 669 |
|
| 670 |
case 'validate':
|
| 671 |
$errors = array();
|
| 672 |
|
| 673 |
// Validation only applies to simple interface
|
| 674 |
if (! $edit[$advanced_form_name]) {
|
| 675 |
if (!$edit['nodeperm_role_edit'] && $edit['nodeperm_role_view']) {
|
| 676 |
$errors['nodeperm_role_edit'] = t('Please choose the role(s) to transition to.');
|
| 677 |
}
|
| 678 |
|
| 679 |
foreach ((array) $edit['nodeperm_role_view'] as $key => $rid) {
|
| 680 |
$edit['nodeperm_role_view'][$key] = $rid;
|
| 681 |
}
|
| 682 |
foreach ((array) $edit['nodeperm_role_edit'] as $key => $rid) {
|
| 683 |
// CRG: view and edit can be different, allow this since other node access modules will be working with this one
|
| 684 |
// $edit['nodeperm_role_view'][$key] = $rid;
|
| 685 |
$edit['nodeperm_role_edit'][$key] = $rid;
|
| 686 |
}
|
| 687 |
}
|
| 688 |
|
| 689 |
foreach ($errors as $name => $message) {
|
| 690 |
form_set_error($name, $message);
|
| 691 |
}
|
| 692 |
|
| 693 |
return count($errors) == 0;
|
| 694 |
break;
|
| 695 |
|
| 696 |
// process the HTML form to store configuration
|
| 697 |
case 'submit':
|
| 698 |
if ($edit[$advanced_form_name]) {
|
| 699 |
// If the new interface then translate the advanced settings into the old style
|
| 700 |
// nodeperm_role_view_inherit - List of roles that inherit the view permission
|
| 701 |
// nodeperm_role_edit_inherit - List of roles that inherit the edit permission
|
| 702 |
$params = array(
|
| 703 |
'nodeperm_role_edit' => array(),
|
| 704 |
'nodeperm_role_view' => array(),
|
| 705 |
'nodeperm_role_edit_inherit' => array(),
|
| 706 |
'nodeperm_role_view_inherit' => array()
|
| 707 |
);
|
| 708 |
|
| 709 |
foreach ( nodeperm_get_roles() as $rid => $role_name) {
|
| 710 |
// Loop through each permission - simular code as in 'form' but different enough to make factor hardly worth the effort
|
| 711 |
$perms = nodeperm_role_perms();
|
| 712 |
foreach ($perms as $p) {
|
| 713 |
$cgi_name = 'nodeperm_role_'. $p .'_' . $rid; // Name of CGI form data
|
| 714 |
$param_name = 'nodeperm_role_' . $p; // Paramater name to save rid in
|
| 715 |
if ($edit[$cgi_name]) {
|
| 716 |
switch ($edit[$cgi_name]) {
|
| 717 |
case '1':
|
| 718 |
$params[$param_name][] = $rid;
|
| 719 |
break;
|
| 720 |
|
| 721 |
case '-':
|
| 722 |
$params[$param_name . '_inherit'][] = $rid;
|
| 723 |
break;
|
| 724 |
|
| 725 |
// N is assumed if not in either array
|
| 726 |
}
|
| 727 |
}
|
| 728 |
|
| 729 |
} // end of loop through permissions
|
| 730 |
} // End of loop through roles
|
| 731 |
}
|
| 732 |
else {
|
| 733 |
// Old (simple) non-inheritance form data
|
| 734 |
// Inherit arrays are cleared just in case this is a transition from advanced to simple
|
| 735 |
$params = array(
|
| 736 |
'nodeperm_role_edit' => $edit['nodeperm_role_edit'],
|
| 737 |
'nodeperm_role_view' => $edit['nodeperm_role_view'],
|
| 738 |
'nodeperm_role_edit_inherit' => array(),
|
| 739 |
'nodeperm_role_view_inherit' => array());
|
| 740 |
}
|
| 741 |
return $params;
|
| 742 |
break;
|
| 743 |
}
|
| 744 |
}
|
| 745 |
|
| 746 |
/*
|
| 747 |
* Returns an array of permissions that are being used.
|
| 748 |
*
|
| 749 |
* Note that internall the code refers to the update grant as 'edit'.
|
| 750 |
* This was retained during addition of inheritence code.
|
| 751 |
*
|
| 752 |
*/
|
| 753 |
|
| 754 |
function nodeperm_role_perms() {
|
| 755 |
if (variable_get('nodeperm_role_mode', 0) == 0) {
|
| 756 |
// Some think permissions slow things down but I have my doubts (CRG) since permissions are all on a single row in node_access
|
| 757 |
// XXX If TRUE then always return view/edit
|
| 758 |
return array('view', 'edit');
|
| 759 |
}
|
| 760 |
else {
|
| 761 |
return array('edit');
|
| 762 |
}
|
| 763 |
}
|
| 764 |
|
| 765 |
/*
|
| 766 |
* matrix theme overview
|
| 767 |
*
|
| 768 |
* The matrix theme is simple to use but a major task to implement. It allows form elements
|
| 769 |
* to be wrapped into a matrix (which is normally rendered as a table). This should be used
|
| 770 |
* only for cases where the input is matrix based. For pure visual arangement a theme
|
| 771 |
* is recomended (see http://drupal.org/node/47582).
|
| 772 |
*
|
| 773 |
* There are 4 new form elements. The [] indicate a key of some name
|
| 774 |
* - matrix : The master object to wrap all others
|
| 775 |
* -- #rows : Array of rows instead of using matrix_row elements (optional, if used DO NOT use matrix_row elements)
|
| 776 |
* -- #header : An array of header cells (like #rows) but replace matrix_head elements (optional, if used DO NOT use matrix_row elements)
|
| 777 |
* -- #caption : caption for table
|
| 778 |
* -- #attributes : HTML attributes to apply to the table
|
| 779 |
* -- [] matrix_head : Use this for a header, use only one of these (or none)
|
| 780 |
* --- #cells : Array of cells instead of using matrix_cell elements (optional, if used DO NOT use matrix_cell elements)
|
| 781 |
* --- There are HTML atributes for this element
|
| 782 |
* --- [] matrix_cell : The cell wraper
|
| 783 |
* ---- '#field' : Used for 'sort' key by theme('table') header processing (optional)
|
| 784 |
* ---- '#sort' : Used for 'sort' key by theme('table') header processing (optional)
|
| 785 |
* ---- '#attributes' = Array of HTML elements for the cell (optional)
|
| 786 |
* ---- [] form array : one per matrix_cell of standard form element
|
| 787 |
* -- [] matrix_row : One row for each row in the matrix
|
| 788 |
* --- #cells : Array of cells instead of using matrix_cell elements (optional, if used DO NOT use matrix_cell elements)
|
| 789 |
* --- '#attributes' = Array of HTML elements for the row (optional)
|
| 790 |
* --- [] matrix_cell : The cell wraper
|
| 791 |
* ---- '#attributes' = Array of HTML elements for the cell (optional)
|
| 792 |
* ---- [] form array : one per matrix_cell of standard form element
|
| 793 |
*
|
| 794 |
* During initial form building the following shortcuts will expand to a full hierarchial form. Processed
|
| 795 |
* in the following order
|
| 796 |
* - matrix_table
|
| 797 |
* - #header : Will create a new element 'row_x' with type '#matrix_head' and all elements placed into '#cells' of that row
|
| 798 |
* - #row : Assumed to be a 2 deimensional array. Will create a new element 'row_x' matrix_row for each element in #rows and within each of those will create a '#cells' entry
|
| 799 |
* - For every non-matrix_row/head entry found will wrap it in a matrix_row element
|
| 800 |
* - WARNINGS:
|
| 801 |
* - Combining #header with normal matrix_head elements is allowed but can get confusing
|
| 802 |
* - Combining #rows with normal matrix_row elements is allowed but can get confusing
|
| 803 |
* - matrix_row/head
|
| 804 |
* - #cells : Will wrap each element in a matrix_cell array. The key will be retaiuned if the array key is non-numeric
|
| 805 |
* - For every non-matrix_cell entry found will wrap it in a matrix_row element, retains the existing key
|
| 806 |
* - WARNINGS:
|
| 807 |
* - Combining the #cells element with normal matrix_cell elements is allowed but can get confusing
|
| 808 |
* - The last matrix_head element encountered is used by the table, all prior onces are ignored
|
| 809 |
* - matrix_cell
|
| 810 |
* - No special processing
|
| 811 |
*
|
| 812 |
*
|
| 813 |
* The fully epanded form is rather cumbersome, but very flexible. A simple 2x2 matrix follows (using just the defualt #markup type for a form)
|
| 814 |
* $form['table'] = array('#type' => 'matrix');
|
| 815 |
* $form['table']['row1'] = array('#type' => 'matrix_row');
|
| 816 |
* $form['table']['row1']['col1'] = array('#type' => 'matrix_cell');
|
| 817 |
* $form['table']['row1']['col1']['r1c1'] = array('#value' => 'r1 c1');
|
| 818 |
* $form['table']['row1']['col2'] = array('#type' => 'matrix_cell');
|
| 819 |
* $form['table']['row1']['col2']['r1c2'] = array('#value' => 'r1 c2');
|
| 820 |
* $form['table']['row2'] = array('#type' => 'matrix_row');
|
| 821 |
* $form['table']['row2']['col1'] = array('#type' => 'matrix_cell');
|
| 822 |
* $form['table']['row2']['col1']['r2c1'] = array('#value' => 'r2 c1');
|
| 823 |
* $form['table']['row2']['col2'] = array('#type' => 'matrix_cell');
|
| 824 |
* $form['table']['row2']['col2']['r2c12] = array('#value' => 'r2 c2');
|
| 825 |
*
|
| 826 |
* The matrix supports a number of shortcuts to make things easier! The matrix will automatically adjust the form to be in the above
|
| 827 |
* hierarchy format so everything works as expected.
|
| 828 |
*
|
| 829 |
* The per-cell wrapper can be eliminated. This makes our 2x2 matrix a bit easier to define. We do loose tha ability to customize
|
| 830 |
* each cell:
|
| 831 |
* $form['table'] = array('#type' => 'matrix');
|
| 832 |
* $form['table']['row1'] = array('#type' => 'matrix_row');
|
| 833 |
* $form['table']['row1']['r1c1'] = array('#value' => 'r1 c1');
|
| 834 |
* $form['table']['row1']'r1c2'] = array('#value' => 'r1 c2');
|
| 835 |
* $form['table']['row2'] = array('#type' => 'matrix_row');
|
| 836 |
* $form['table']['row2']['r2c1'] = array('#value' => 'r2 c1');
|
| 837 |
* $form['table']['row2']['r2c12] = array('#value' => 'r2 c2');
|
| 838 |
*
|
| 839 |
* The matrix_row can be dropped and an array saved in the base matrix #row element can be used. In this example
|
| 840 |
* I also used the shortcut from above. You can of course define the full matrix_col structure if you wish. I
|
| 841 |
* find myself using this convention quit a lot in code. It's a nice compromise between flexibility and easy
|
| 842 |
* of use. Only the discrete control over indvidual cell attributes are lost.
|
| 843 |
* $form['table'] = array('#type' => 'matrix');
|
| 844 |
* $form['table']['#rows']= array(
|
| 845 |
* array('col1' => array('#value' => 'r1 c1'), array('col2' => array('#value' => 'r1 c2')),
|
| 846 |
* array('col1' => array('#value' => 'r2 c1'), array('col2' => array('#value' => 'r2 c2')),
|
| 847 |
* )
|
| 848 |
*
|
| 849 |
* A row can be repsented as an array of columns using the '#cells' attribute for matrix_row and matrix_head
|
| 850 |
* $form['table'] = array('#type' => 'matrix');
|
| 851 |
* $form['table']['row1'] = array('#type' => 'matrix_row');
|
| 852 |
* $form['table']['row1']['#cells'] = array(array('#value' => 'r1 c1'), array('#value' => 'r1 c2'));
|
| 853 |
* $form['table']['row2']['#cells'] = array(array('#value' => 'r2 c1'), array('#value' => 'r2 c2'));
|
| 854 |
*
|
| 855 |
* A pure 2 dimensional array can be used for #rows to combine rows and cells:
|
| 856 |
* $form['table'] = array('#type' => 'matrix');
|
| 857 |
* $form['table']['#rows'] = array(
|
| 858 |
* array(array('#value' => 'r1 c1'), array('#value' => 'r1 c2')),
|
| 859 |
* array(array('#value' => 'r2 c1'), array('#value' => 'r2 c2')),
|
| 860 |
* );
|
| 861 |
*
|
| 862 |
* If the form value is a constant, like the above example, you can drop the array building:
|
| 863 |
* $form['table'] = array('#type' => 'matrix');
|
| 864 |
* $form['table']['#rows'] = array(
|
| 865 |
* array('r1 c1', 'r1 c2'),
|
| 866 |
* array('r2 c1', 'r2 c2'),
|
| 867 |
* );
|
| 868 |
*
|
| 869 |
* Finally all of these can be combined and mixed and matched with the following limitations:
|
| 870 |
* * Do NOT use matrix_table '#rows' and explictly named matrix_row, this will cause untold havoc
|
| 871 |
* * Do NOT use matrix_row/head '#cells' and explictly named matrix_cells for the same row, this will cause untold havoc
|
| 872 |
* * Drupal forms API requires name for input form elements. If the matrix_cell is not explictly provided then the matrix
|
| 873 |
* handler for create a name, which is probably not what you want. In this case I recomend using '#name' and setting the CGI
|
| 874 |
* name properly. In almost all cases (submit being the exception) you will want to use the template 'edit[%s]' to build the name.
|
| 875 |
*
|
| 876 |
* Additional limitations:
|
| 877 |
* * There is no automated way to create spanned rows or columns. I have not coded anything to prevent this but I have not
|
| 878 |
* tested it in anyway.
|
| 879 |
*
|
| 880 |
*
|
| 881 |
* The Drupal theme('table') is used to render the final table. Due to the mismatch between that theme and
|
| 882 |
* the way form cde must be hierarchial there is some magic that happens. Bascially data is serialized
|
| 883 |
* during rendering and then de-serialized and re-organized when the matrix is finally generated.
|
| 884 |
*
|
| 885 |
* History:
|
| 886 |
* 2005-09-06
|
| 887 |
* * Code was originally based on http://drupal.org/node/51726 which gave me the background I needed to implement my own matrix
|
| 888 |
* * Code updated to allow many ways of building the matrix
|
| 889 |
* * Uses the theme('table') in Drupal for maximum compatibility
|
| 890 |
*/
|
| 891 |
|
| 892 |
define('FORM_MATRIX_DELIMITER', "\n{Br}\n"); // This should be unique enough to not appear in any serialized data
|
| 893 |
|
| 894 |
/*
|
| 895 |
* Theme the matrix_cell for a form. Returns serialized data for use by matrix_row/head
|
| 896 |
*
|
| 897 |
*/
|
| 898 |
|
| 899 |
function theme_matrix_cell($element) {
|
| 900 |
# drupal_set_message('matrix_cell: <pre>'. print_r($element,1). '</pre>');
|
| 901 |
|
| 902 |
// Organize data so it is suitable for theme('table')
|
| 903 |
// Note on #atributes
|
| 904 |
// All HTML attributes are allowed
|
| 905 |
// 'field', 'sort' : Supported if this cell is part of a matrix_head, otherwise these will be ignored (handled by theme_matrix_head)
|
| 906 |
$rtn = array('data' => $element['#children']);
|
| 907 |
if($element['parent'] == 'matrix_head') {
|
| 908 |
foreach (array('field', 'sort') as $k) {
|
| 909 |
if ($element['#' . $k]) {
|
| 910 |
$rtn[$k] = $element['#'. $k];
|
| 911 |
}
|
| 912 |
}
|
| 913 |
}
|
| 914 |
|
| 915 |
# Normal HTML attributes
|
| 916 |
if (is_array($element['#attributes'])) {
|
| 917 |
$rtn = array_merge($rtn, $element['#attributes']);
|
| 918 |
}
|
| 919 |
|
| 920 |
// Return this data as a string so form builder can handle it. Add seperator for easier parsing
|
| 921 |
return serialize($rtn) . NODE_MATRIX_DELIMITER; # A special delimiter that should not appear in where else in the serialized string
|
| 922 |
}
|
| 923 |
function theme_matrix_head($element) {
|
| 924 |
// Convert children cells back to data elements
|
| 925 |
// - There is no matrix_head specific attributes supported by theme('table') and no mechanism to pass thm
|
| 926 |
// -- 'sort', 'field' attributes can be passed but they must be done via matrix_cell #attributes
|
| 927 |
foreach (explode(NODE_MATRIX_DELIMITER,$element['#children']) as $s) {
|
| 928 |
$rtn['head'][] = unserialize($s);
|
| 929 |
}
|
| 930 |
|
| 931 |
// Return this data as a string, formtable will process it correctly
|
| 932 |
return serialize($rtn) . NODE_MATRIX_DELIMITER; # A special delimiter that should not appear in where else in the serialized string
|
| 933 |
}
|
| 934 |
|
| 935 |
/*
|
| 936 |
* Theme for rows. Takes all children element, de-setrializes them and re-serializes it in a rows array
|
| 937 |
*/
|
| 938 |
function theme_matrix_row($element) {
|
| 939 |
// Convert children cells back to data elements
|
| 940 |
foreach (explode(NODE_MATRIX_DELIMITER, $element['#children']) as $s) {
|
| 941 |
$rtn['row']['data'][] = unserialize($s);
|
| 942 |
}
|
| 943 |
|
| 944 |
// Support attributes
|
| 945 |
if (is_array($element['#attributes'])) {
|
| 946 |
$rtn = array_merge($rtn, $element['#attributes']);
|
| 947 |
}
|
| 948 |
|
| 949 |
// Return this data as a string so form builder can handle it. Add seperator for easier parsing
|
| 950 |
return serialize($rtn) . NODE_MATRIX_DELIMITER; # A special delimiter that should not appear in where else in the serialized string
|
| 951 |
}
|
| 952 |
|
| 953 |
/*
|
| 954 |
* Render the crg_formtable form element. This is a simple element that
|
| 955 |
* is table based:
|
| 956 |
* '#type' => 'matrix'
|
| 957 |
* '#header' => array or strings OR array of form elements
|
| 958 |
* - Strings are quoted for html enties
|
| 959 |
* '#rows' => An array for each row where each row is an array of columns where each column is a form arrayA
|
| 960 |
* '#caption' => Table caption
|
| 961 |
* '#attributes' => Table attributes
|
| 962 |
*
|
| 963 |
*
|
| 964 |
* WARNING: When using the #rows attribute be sure that form elements are explictly named
|
| 965 |
* using '#name' of the applicable form cell. In addition you may want to make sure the
|
| 966 |
* name is of the form 'edit[%s]' otherwise you may not get the _POST data you are expecting.
|
| 967 |
* matrix_cell elements explictly created do not suffer this problem and the cell
|
| 968 |
*/
|
| 969 |
|
| 970 |
function theme_matrix($element) {
|
| 971 |
$header = array();
|
| 972 |
$rows = array();
|
| 973 |
|
| 974 |
foreach (explode(NODE_MATRIX_DELIMITER, $element['#children']) as $s) {
|
| 975 |
if (! $s) {
|
| 976 |
continue;
|
| 977 |
}
|
| 978 |
|
| 979 |
$data = unserialize($s);
|
| 980 |
if($data['head']) {
|
| 981 |
$header = $data['head'];
|
| 982 |
} elseif($data['row']) {
|
| 983 |
$rows[] = $data['row'];
|
| 984 |
} else {
|
| 985 |
drupal_set_message("System Error: Unknown matrix element found in final rendering. Original data was:<pre>$s</pre>Decoded to:<pre>". htmlentities(print_r($data,1)). "</pre>");
|
| 986 |
}
|
| 987 |
}
|
| 988 |
|
| 989 |
return theme('table', $header, $rows, $element['#attributes'], $element['#caption']);
|
| 990 |
}
|
| 991 |
|
| 992 |
|
| 993 |
/**
|
| 994 |
* return custom fields for my new element definitions
|
| 995 |
*/
|
| 996 |
function nodeperm_role_elements(){
|
| 997 |
$type['matrix_row'] = array('#process' => array('matrix_ensure_cells' => array()), '#tree' => TRUE );
|
| 998 |
$type['matrix_head'] = array('#process' => array('matrix_ensure_cells' => array()), '#tree' => TRUE );
|
| 999 |
$type['matrix'] = array('#process' => array('matrix_ensure_rows' => array()), '#tree' => TRUE );
|
| 1000 |
return $type;
|
| 1001 |
}
|
| 1002 |
/**
|
| 1003 |
* Given a row element, the only valid children are cells
|
| 1004 |
* If the child is NOT a cell, wrap it up as if it were one.
|
| 1005 |
*
|
| 1006 |
* If '#cells' is present then wrap every element in that array as a cell.
|
| 1007 |
* #cells and normal hierarhcy are mutually exclusive.
|
| 1008 |
*/
|
| 1009 |
function matrix_ensure_cells($element){
|
| 1010 |
|
| 1011 |
if ($element['#cells']) {
|
| 1012 |
$element = array_merge($element, _matrix_cell_array_to_tree($element['#cells']));
|
| 1013 |
// Remove this element, it is just taking up room now
|
| 1014 |
unset($element['#cells']);
|
| 1015 |
}
|
| 1016 |
|
| 1017 |
// Scan through every non-attribute value and verify it is a matrix_cell
|
| 1018 |
foreach($element as $key=>$cell){
|
| 1019 |
if(! is_array($cell)){continue;}
|
| 1020 |
if($key[0] == '#'){continue;} # Skip atributes
|
| 1021 |
if($cell['#type'] != 'matrix_cell'){
|
| 1022 |
// Expand the cell into a matrix_cell element (matrix_row / matrix_cell / form-data)
|
| 1023 |
// - Retain the original key so any form data can build CGI names properly. This is a bit confusing
|
| 1024 |
// since the row and the cell have the same key (nested so '[test]' ==> '[test][test]') but there is no programatic reason
|
| 1025 |
// to alter this. Any alteration woudl require creating a new key and unsetting the old, which will impact performance
|
| 1026 |
$element[$key] = array('#type' => 'matrix_cell', $key => $cell );
|
| 1027 |
}
|
| 1028 |
}
|
| 1029 |
|
| 1030 |
return $element;
|
| 1031 |
}
|
| 1032 |
|
| 1033 |
/*
|
| 1034 |
* Given a matrix element
|
| 1035 |
* make sure every non-attribute entry in the matrix is a matrix_head or matrix_row.
|
| 1036 |
* Also support the '#rows' and '#header' attributes for quick simple matrices
|
| 1037 |
*/
|
| 1038 |
function matrix_ensure_rows($element){
|
| 1039 |
// If the '#rows' is defined then this is an array of [rows][columns]
|
| 1040 |
if (is_array($element['#rows'])) {
|
| 1041 |
$row_count = 1;
|
| 1042 |
|
| 1043 |
// If a '#header' is defined then create an entry for it
|
| 1044 |
if (is_array($element['#header'])) {
|
| 1045 |
$element['row_'. $row_count++] = array(
|
| 1046 |
'#type' => 'matrix_head',
|
| 1047 |
'#cells' => $element['#header'],
|
| 1048 |
);
|
| 1049 |
|
| 1050 |
// Remove this element, it is just taking up room now
|
| 1051 |
unset($element['#header']);
|
| 1052 |
}
|
| 1053 |
|
| 1054 |
// Convert row/col hierarchy to tree structure
|
| 1055 |
foreach( $element['#rows'] as $row) {
|
| 1056 |
$element['row_'. $row_count++] = array(
|
| 1057 |
'#type' => 'matrix_row',
|
| 1058 |
// An array of 'col_x' entries
|
| 1059 |
#_matrix_row_cell_to_tree($row),
|
| 1060 |
'#cells' => $row,
|
| 1061 |
);
|
| 1062 |
|
| 1063 |
}
|
| 1064 |
// Remove this element, it is just taking up room now
|
| 1065 |
unset($element['#rows']);
|
| 1066 |
}
|
| 1067 |
|
| 1068 |
// Assume a hierarchial structure (tree like)
|
| 1069 |
// Every non-attribute element of a marix must be a matrix_row or matrix_head. If neither then wrap into a matrix_row
|
| 1070 |
foreach($element as $key=>$cell){
|
| 1071 |
if(! is_array($cell)){continue;}
|
| 1072 |
if($key[0] == '#'){continue;} # Skip attributes
|
| 1073 |
if(! in_array($cell['#type'] , array('matrix_head','matrix_row') ) ){
|
| 1074 |
$element[$key] = array_merge(array('#type' => 'matrix_row'), $cell );
|
| 1075 |
}
|
| 1076 |
}
|
| 1077 |
|
| 1078 |
#drupal_set_message('matrix_ensure_rows: <pre>'. print_r($element,1). '</pre>');
|
| 1079 |
return $element;
|
| 1080 |
}
|
| 1081 |
|
| 1082 |
|
| 1083 |
/*
|
| 1084 |
* Given an array of cells (containing scalars or form arrays) return
|
| 1085 |
* an array of form elements keyed by 'cell_x' where x starts at 1 and
|
| 1086 |
* increments for each cell
|
| 1087 |
*/
|
| 1088 |
|
| 1089 |
function _matrix_cell_array_to_tree($row) {
|
| 1090 |
|
| 1091 |
$cell_count = 1;
|
| 1092 |
$cell_data = array(); // Cell data wrapped in matrix
|
| 1093 |
|
| 1094 |
// Each element of the header is either a single value or a form API call
|
| 1095 |
//
|
| 1096 |
// Note that this function is complicated by the fact that forms API works in a strict hierarchy. This means
|
| 1097 |
// that the matrix must be wrapped AROUND the form data creating a tree that is two elements deep.
|
| 1098 |
foreach ($row as $k => $cell) {
|
| 1099 |
if (is_array($cell)) {
|
| 1100 |
if($cell['#type'] == 'matrix_cell') {
|
| 1101 |
// Data is already in a matrix_cell structure so no further action is needed
|
| 1102 |
$cell_data = $cell;
|
| 1103 |
} else {
|
| 1104 |
// Assume form element and wrap it in a matrix
|
| 1105 |
// - If the key is not numeric then use the text as the cell name (preserve CGI name), otherwise use the constant 'cell'
|
| 1106 |
// - the name of the cell key can be anything since only one element is allowed per matrix_cell
|
| 1107 |
if (is_numeric($k)) {
|
| 1108 |
$cell_data = array('#type' => 'matrix_cell', 'cell' => $cell);
|
| 1109 |
} else
|
| 1110 |
{
|
| 1111 |
$cell_data = array('#type' => 'matrix_cell', $k => $cell);
|
| 1112 |
}
|
| 1113 |
}
|
| 1114 |
}
|
| 1115 |
else {
|
| 1116 |
// Convert to form element and wrap it in a matrix - the name of the cell key can be anything since only one element is allowed per matrix_cell
|
| 1117 |
$cell_data = array('#type' => 'matrix_cell', 'cell' => array('#value' => $cell));
|
| 1118 |
}
|
| 1119 |
|
| 1120 |
// Caller will append this keyed array to the matrix row type element so every key must be unique
|
| 1121 |
$data['cell_'. $cell_count++] = $cell_data;
|
| 1122 |
}
|
| 1123 |
|
| 1124 |
return $data;
|
| 1125 |
}
|
| 1126 |
|
| 1127 |
// vim: ft=php softtabstop=2 tabstop=2 shiftwidth=2
|