/[drupal]/contributions/modules/nodeperm_role/nodeperm_role.module
ViewVC logotype

Contents of /contributions/modules/nodeperm_role/nodeperm_role.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.11 - (show annotations) (download) (as text)
Sat Jun 10 17:30:03 2006 UTC (3 years, 5 months ago) by robb
Branch: MAIN
CVS Tags: HEAD
Changes since 1.10: +68 -25 lines
File MIME type: text/x-php
Fixed problem with repair button not working
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 &quot;administer nodes&quot; 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

  ViewVC Help
Powered by ViewVC 1.1.2