' . t('The node privacy by role module allows users, when creating or editing a post, to select which roles of users on a site will have view permissions for the node and which users on a site will have edit permissions. Community leaders frequently want to give permissions to roles to create and manage content for a site. The ability to publish information, that would traditionally be hoarded, allows communities to educate each other while still preserving the value of knowledge.') . '

'; $output .= '

' . t('The node privacy by-role permissions are set by users for their nodes. If the node privacy by role module is disabled, the default permissions scheme will be in effect again, in which all users have view permissions for all nodes. However, if the module is re-enabled, the node-by-node permissions that were in place during the previous period in which the module was enabled will take effect again. Roles given edit permissions are automatically given view permissions even if the user tries to give edit permissions to a particular role, but not view permissions.') . '

'; $output .= t('

You can:

', array('@admin-node-configure-types' => url('admin/structure/types'))); $output .= '

' . t('For more information please read the configuration and customization handbook Node privacy by role page.', array('@node_privacy_byrole' => 'http://www.drupal.org/handbook/modules/node_privacy_byrole/')) . '

'; return $output; } } /** * Simple function to identify when module is being disabled * so that as the node_access table is rebuilt, it doesn't set * any permissions for this module */ function node_privacy_byrole_disabling($set = NULL) { static $disabling = FALSE; if ($set != NULL) { $disabling = $set; } return $disabling; } /** * Implements hook_node_grants(). */ function node_privacy_byrole_node_grants($account, $op) { return array( 'node_privacy_byrole_role' => array_keys($account->roles), 'node_privacy_byrole_user' => array($account->uid), ); } /** * Implements hook_node_access_records(). */ function node_privacy_byrole_node_access_records($node) { if (node_privacy_byrole_disabling()) { return; } // if node is not published don't allow // this module to override default mechanism // we do not want anonymous users to access unpublished content // when view is enabled by default for a role, // it overrides the check for published or not if ($node->status == 0) { return; } node_privacy_byrole_nodeapi_prepare($node); $grants = array(); // permission for node owner if ($node->uid > 0) { $grants[] = array( 'realm' => 'node_privacy_byrole_user', 'gid' => $node->uid, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0, 'priority' => 0, ); } // permission for node roles foreach (array_keys(user_roles()) as $rid) { $edit_perm = empty($node->node_privacy_byrole['roles'][$rid]['edit']) ? 0 : 1; $delete_perm = empty($node->node_privacy_byrole['roles'][$rid]['delete']) ? 0 : 1; $view_perm = ($edit_perm || $delete_perm) ? 1 : (empty($node->node_privacy_byrole['roles'][$rid]['view']) ? 0 : 1); $grants[] = array( 'realm' => 'node_privacy_byrole_role', 'gid' => $rid, 'grant_view' => $view_perm, 'grant_update' => $edit_perm, 'grant_delete' => $delete_perm, 'priority' => 0, ); } return $grants; } /** * Implements hook_node_load(). */ function node_privacy_byrole_node_load($nodes, $types) { foreach ($nodes as $node) { node_privacy_byrole_nodeapi_prepare($node); } } /** * Implements hook_node_prepare(). */ function node_privacy_byrole_node_prepare($node) { node_privacy_byrole_nodeapi_prepare($node); } /** * Implements hook_node_delete(). */ function node_privacy_byrole_node_delete($node) { $num_deleted = db_delete('node_privacy_byrole') ->condition('nid', $node->nid) ->execute(); watchdog('node', '@num_deleted node privacy byrole rules deleted for node @nid', array('@num_deleted' => $num_deleted, '@nid' => $node->nid), WATCHDOG_DEBUG); } /** * Implements hook_node_insert(). */ function node_privacy_byrole_node_insert($node) { $roles = array_keys(user_roles()); node_privacy_byrole_nodeapi_prepare($node); // http://drupal.org/node/256396 foreach ($roles as $rid) { db_insert('node_privacy_byrole') ->fields(array( 'nid' => $node->nid, 'gid' => $rid, 'realm' => 'node_privacy_byrole_role', 'grant_view' => $node->node_privacy_byrole['roles'][$rid]['view'], 'grant_update' => $node->node_privacy_byrole['roles'][$rid]['edit'], 'grant_delete' => $node->node_privacy_byrole['roles'][$rid]['delete'], )) ->execute(); } db_insert('node_privacy_byrole') ->fields(array( 'nid' => $node->nid, 'gid' => $node->uid, 'realm' => 'node_privacy_byrole_user', 'grant_view' => 1, 'grant_update' => 1, 'grant_delete' => 1, )) ->execute(); } /** * Implements hook_node_update(). */ function node_privacy_byrole_node_update($node) { $roles = array_keys(user_roles()); node_privacy_byrole_nodeapi_prepare($node); // http://drupal.org/node/153588 // As a new role might have been added since creation of the node, we cannot simply "update" and so delete and reinsert db_delete('node_privacy_byrole') ->condition(db_and()->condition('nid', $node->nid)->condition('realm', 'node_privacy_byrole_role')) ->execute(); foreach ($roles as $rid) { db_insert('node_privacy_byrole') ->fields(array( 'nid' => $node->nid, 'gid' => $rid, 'realm' => 'node_privacy_byrole_role', 'grant_view' => $node->node_privacy_byrole['roles'][$rid]['view'], 'grant_update' => $node->node_privacy_byrole['roles'][$rid]['edit'], 'grant_delete' => $node->node_privacy_byrole['roles'][$rid]['delete'], )) ->execute(); } // Record for owner exists for sure, so we can simply update it db_update('node_privacy_byrole') ->fields(array( 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0, )) ->condition(db_and()->condition('nid', $node->nid)->condition('gid', $node->uid)->condition('realm', 'node_privacy_byrole_user')) ->execute(); } /** * Adds permissions to the node object. */ function node_privacy_byrole_nodeapi_prepare(&$node) { if (!isset($node->node_privacy_byrole)) { $roles = array_keys(user_roles()); $perms = array('view', 'edit', 'delete'); if (isset($node->nid) && empty($node->is_new)) { // this is an existing node, get current permissions $result = db_select('node_privacy_byrole', 'npbr') ->fields('npbr', array('gid', 'grant_view', 'grant_update', 'grant_delete')) ->condition(db_and()->condition('nid', $node->nid)->condition('realm', 'node_privacy_byrole_role')) ->execute(); $current_perms = array(); foreach ($result as $role) { $current_perms[$role->gid]['view'] = $role->grant_view; $current_perms[$role->gid]['edit'] = $role->grant_update; $current_perms[$role->gid]['delete'] = $role->grant_delete; } while (list(, $rid) = each($roles)) { // if the role exists in node_privacy_byrole table, fill its perm with the database ones if (isset($current_perms[$rid])) { while (list(, $perm) = each($perms)) { $node->node_privacy_byrole['roles'][$rid][$perm] = $current_perms[$rid][$perm] ? 1 : 0; } } else { // else : the role doesn't exist in node_privacy_byrole table (it has been recently added). // In this case, fill perms with default ones while (list(, $perm) = each($perms)) { $default_roles = _node_privacy_byrole_get_default_roles($node->type, $perm); $node->node_privacy_byrole['roles'][$rid][$perm] = in_array($rid, $default_roles) ? 1 : 0; } } reset($perms); } } else { // case where node is being created while (list(, $rid) = each($roles)) { while (list(, $perm) = each($perms)) { $default_roles = _node_privacy_byrole_get_default_roles($node->type, $perm); $node->node_privacy_byrole['roles'][$rid][$perm] = in_array($rid, $default_roles) ? 1 : 0; } reset($perms); } } } } /** * Implements hook_form_alter(). */ function node_privacy_byrole_form_alter(&$form, &$form_state, $form_id) { if (!empty($form['#node_edit_form'])) { if (node_privacy_byrole_user_has_meta_permission($form['#node'])) { node_privacy_byrole_node_form($form); } } } /** * Implements hook_form_FORM_ID_alter(). */ function node_privacy_byrole_form_node_type_form_alter(&$form, &$form_state) { node_privacy_byrole_node_type_form($form); } /** * Implements hook_form_FORM_ID_alter(). */ function node_privacy_byrole_form_node_configure_alter(&$form, &$form_state) { $form['npbr_default_permissions'] = array( '#type' => 'checkbox', '#title' => 'When rebuilding permissions, reset the node privacy by role permissions on all nodes to the content type defaults.', '#weight' => 0, '#default_value' => variable_get('npbr_default_permissions', 0), ); $form['access']['#weight'] = -1; } /** * Implements hook_form_FORM_ID_alter(). */ function node_privacy_byrole_form_node_configure_rebuild_confirm_alter(&$form, &$form_state) { if (variable_get('npbr_default_permissions', 0)) { // Make sure the npbr handler runs first. $form['#submit'] = array_merge(array('node_privacy_byrole_rebuild_submit'), $form['#submit']); } } /** * Wipes out all saved permissions. */ function node_privacy_byrole_rebuild_submit($form, &$form_state) { db_query('TRUNCATE TABLE {node_privacy_byrole}'); } /** * Checks that the active user has access to set permissions on nodes. */ function node_privacy_byrole_user_has_meta_permission($node) { global $user; if ($user->uid == 1) { return TRUE; } $permitted_roles = _node_privacy_byrole_get_default_roles($node->type, 'grant'); $user_roles = array_keys($user->roles); return count(array_intersect($permitted_roles, $user_roles)) ? TRUE : FALSE; } /** * Returns those role id's that are permitted to do $action on a $type node */ function _node_privacy_byrole_get_default_roles($type, $action) { return variable_get('npbr_default_' . $action . '_perms_' . $type, array()); } /** * Form elements that extends node type form * Used for set the default permission settings per node type */ function node_privacy_byrole_node_type_form(&$form) { $type = $form['#node_type']->type; $roles = user_roles(); $perms = array( 'view' => array( 'title' => t('Default View Permissions'), 'description' => t('Select roles to be given view permisions by default.'), ), 'edit' => array( 'title' => t('Default Edit Permissions'), 'description' => t('Select roles to be given edit permissions by default.'), ), 'delete' => array( 'title' => t('Default Delete Permissions'), 'description' => t('Select roles to be given delete permissions by default.'), ), 'grant' => array( 'title' => t('Roles with rights to update permissions'), 'description' => t('Select which roles will have rights to alter permissions on nodes.'), ), ); $form['npbr_workflow_settings'] = array( '#type' => 'fieldset', '#title' => t('Node privacy by role'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); while (list($perm, $permdata) = each($perms)) { $permname = 'npbr_default_' . $perm . '_perms'; $form['npbr_workflow_settings'][$permname] = array( '#tree' => TRUE, '#type' => 'fieldset', '#title' => $permdata['title'], '#collapsible' => FALSE, '#collapsed' => FALSE, '#description' => $permdata['description'], '#weight' => 0, ); $default_perms = _node_privacy_byrole_get_default_roles($type, $perm); foreach (array_keys($roles) as $rid) { $form['npbr_workflow_settings'][$permname][$rid] = array( '#type' => 'checkbox', '#title' => $roles[$rid], '#return_value' => 1, '#default_value' => in_array($rid, $default_perms), ); } } } /** * Form elements that extends node edit form */ function node_privacy_byrole_node_form(&$form) { $roles = array_keys(user_roles()); $perms = array('view', 'edit', 'delete'); // create checkboxes for every role and every permission // later depending on the $op variable only the default values will be updated $form['node_privacy_byrole'] = array( '#tree' => TRUE, '#theme' => 'node_privacy_byrole_node_form', '#type' => 'fieldset', '#title' => t('View/Edit Permissions'), '#description' => t('Select which users can view/edit your post based on their role.'), '#collapsible' => TRUE, // collapse depends on bug when viewing tables included in collapsed fielsets in IE7: // http://drupal.org/node/237565 '#collapsed' => strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 7.0') > 0 ? FALSE : TRUE, '#weight' => 9, ); while (list(, $rid) = each($roles)) { $form['node_privacy_byrole']['roles'][$rid] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#collapsed' => FALSE, '#weight' => 0, ); while (list(, $perm) = each($perms)) { $form['node_privacy_byrole']['roles'][$rid][$perm] = array( '#type' => 'checkbox', '#title' => NULL, '#return_value' => 1, '#default_value' => $form['#node']->node_privacy_byrole['roles'][$rid][$perm], ); } reset($perms); } } /** * Implements hook_theme(). */ function node_privacy_byrole_theme() { return array( 'node_privacy_byrole_node_form' => array( 'render element' => 'form', ), ); } /** * Theming permission settings form elements on node edit form. */ function theme_node_privacy_byrole_node_form($variables) { $form = $variables['form']; $roles = user_roles(); $header = array(t('Role'), t('View'), t('Edit'), t('Delete')); $rows = array(); // every row is a role foreach (element_children($form['roles']) as $rid) { $row = array(); $row[] = $roles[$rid]; // every column is a permission foreach (element_children($form['roles'][$rid]) as $i) { $row[] = drupal_render($form['roles'][$rid][$i]); } $rows[] = $row; } return theme('table', array('header' => $header, 'rows' => $rows)); } /** * Implements hook_node_operations(). * * @return array */ function node_privacy_byrole_node_operations() { $operations = array(); foreach (user_roles() as $rid => $role) { $grants = array(); $revokes = array(); foreach (array('view', 'edit', 'delete') as $priv) { $operations["grant-$rid-$priv"]['label'] = t('Grant !priv for !role', array('!priv' => $priv, '!role' => $role)); $operations["grant-$rid-$priv"]['callback'] = 'node_privacy_byrole_node_operations_update'; $operations["grant-$rid-$priv"]['callback arguments'] = array(1, $rid, $priv); $operations["revoke-$rid-$priv"]['label'] = t('Revoke !priv for !role', array('!priv' => $priv, '!role' => $role)); $operations["revoke-$rid-$priv"]['callback'] = 'node_privacy_byrole_node_operations_update'; $operations["revoke-$rid-$priv"]['callback arguments'] = array(0, $rid, $priv); } } return $operations; } /** * Callback to perform mass operations to granting or revoking permissions. */ function node_privacy_byrole_node_operations_update($nodes, $perm, $rid, $priv) { foreach ($nodes as $node_to_update) { $node = node_load($node_to_update); $node->node_privacy_byrole['roles'][$rid][$priv] = $perm; node_save($node); } } /** * Implements hook_action_info(). */ function node_privacy_byrole_action_info() { return array( 'node_privacy_byrole_change_role_action' => array( 'label' => t('Change role permissions'), 'configurable' => TRUE, 'type' => 'node', 'behavior' => array('changes_property'), 'triggers' => array( 'node_insert', 'node_update', ), ), 'node_privacy_byrole_rolereference_action' => array( 'label' => t('Change permissions based on rolereference field'), 'type' => 'node', 'configurable' => TRUE, 'behavior' => array('changes_property'), 'triggers' => array( 'node_insert', 'node_update', ), ), ); } /** * Actions callback to change role permissions. */ function node_privacy_byrole_change_role_action($node, $context) { /* Mod $node here to have new permissions. It expects: $node->node_privacy_byrole['roles'][$rid]['view'] $node->node_privacy_byrole['roles'][$rid]['edit'] $node->node_privacy_byrole['roles'][$rid]['delete'] Otherwise it will use defaults setup for the content type. */ if (isset($context['node_privacy_byrole']['roles'])) { $node->node_privacy_byrole['roles'] = $context['node_privacy_byrole']['roles']; } module_invoke('node_privacy_byrole', 'node_update', $node); $node_link = l(t('view'), 'node/' . $node->nid); watchdog('action', 'Updated @type %title permissions.', array('@type' => node_type_get_name($node), '%title' => $node->title), WATCHDOG_NOTICE, $node_link); } /** * Actions form for configuring role permissions changes. */ function node_privacy_byrole_change_role_action_form($context) { $form = array(); $form['#node']->node_privacy_byrole = isset($context['node_privacy_byrole']) ? $context['node_privacy_byrole'] : NULL; // Avoiding a notice since menu_form_alter assumes if $form['#node'] is set, $form['#node']->type will be also. $form['#node']->type = NULL; node_privacy_byrole_node_form($form); $form['node_privacy_byrole']['#weight'] = 0; $form['node_privacy_byrole']['#collapsed'] = FALSE; return $form; } /** * Validation hook for making sure the proper permissions form was used. */ function node_privacy_byrole_change_role_action_validate($form, &$form_state) { $errors = array(); if (empty($form_state['values']['node_privacy_byrole'])) { $errors['node_privacy_byrole'] = t('Please use the supplied form to submit access permissions.'); } foreach ($errors as $name => $message) { form_set_error($name, $message); } return count($errors) == 0; } /** * Submission hook for saving action settings. */ function node_privacy_byrole_change_role_action_submit($form, &$form_state) { return array('node_privacy_byrole' => $form_state['values']['node_privacy_byrole']); } /** * Actions callback for changing permissions on rolereference fields. */ function node_privacy_byrole_rolereference_action($node, $context) { /* Mod $node here to have new permissions. It expects: $node->node_privacy_byrole['roles'][$rid]['view'] $node->node_privacy_byrole['roles'][$rid]['edit'] $node->node_privacy_byrole['roles'][$rid]['delete'] Otherwise it will use defaults setup for the content type. */ if (isset($context['node_privacy_byrole']['roles'])) { $node->node_privacy_byrole['roles'] = $context['node_privacy_byrole']['roles']; } $node->node_privacy_byrole['author'] = $context['node_author']; foreach ($context['permissions'] as $key => $value) { // The true $values are normally literally 'view', 'edit', 'delete' or 0 for false // which is required for the #default_values when loading the configuration // page for an existing action, but the node access table actually works on // 0 or 1 in the access tables, so convert them here because that's easier // than converting 1 to 'edit' for the #default_value on the form case. $context['permissions'][$key] = empty($context['permissions'][$key]) ? 0 : 1; } foreach (_node_privacy_byrole_walk_role_fields($node->$context['field']) as $rid) { $node->node_privacy_byrole['roles'][$rid] = $context['permissions']; } module_invoke('node_privacy_byrole', 'node_update', $node); $node_link = l(t('view'), 'node/' . $node->nid); watchdog('action', 'Updated @type %title permissions from rolereference value.', array('@type' => node_type_get_name($node), '%title' => $node->title), WATCHDOG_NOTICE, $node_link); } /** * Actions form for configuring rolereference field permissions. */ function node_privacy_byrole_rolereference_action_form($context) { $form = array(); $form['#node']->node_privacy_byrole = isset($context['node_privacy_byrole']) ? $context['node_privacy_byrole'] : NULL; node_privacy_byrole_node_form($form); unset($form['node_privacy_byrole']['#weight']); $form['node_privacy_byrole']['#title'] = t('Default permissions'); $form['new'] = array( '#type' => 'fieldset', '#title' => t('New permission'), '#description' => t('The new permissions to set that will override the defaults. The value of the field is expected to match a numerical role id.'), '#collapsed' => TRUE, '#collapsible' => TRUE, ); // $fields = field_info_instances('node'); $fields = field_info_fields(); $options = array(); foreach ($fields as $field) { // $options[$field['field_name']] = t($field['widget']['label']) . ' (' . $field['field_name'] . ')'; $options[$field['field_name']] = $field['field_name']; } asort($options, SORT_LOCALE_STRING); $form['new']['field'] = array( '#title' => t('Field'), '#type' => 'select', '#options' => $options, '#default_value' => isset($context['field']) ? $context['field'] : '', ); unset($fields, $field, $options); $form['new']['permissions'] = array( '#title' => t('Permissions'), '#type' => 'checkboxes', '#options' => array( 'view' => t('view'), 'edit' => t('edit'), 'delete' => t('delete'), ), '#default_value' => isset($context['permissions']) ? $context['permissions'] : '', '#description' => t('Edit permission implies view. Delete permission implies edit.'), ); $form['author'] = array( '#type' => 'fieldset', '#title' => t('Node Author'), '#collapsed' => TRUE, '#collapsible' => TRUE, ); $form['author']['node_author'] = array( '#type' => 'checkboxes', '#options' => array( 'view' => t('view'), 'edit' => t('edit'), 'delete' => t('delete'), ), '#default_value' => isset($context['node_author']) ? $context['node_author'] : '', '#description' => t('Edit permission implies view. Delete permission implies edit.'), ); return $form; } /** * Validation hook for making sure the proper permissions form was used. */ function node_privacy_byrole_rolereference_action_validate($form, &$form_state) { $errors = array(); if (empty($$form_state['values']['node_privacy_byrole']) && empty($form_state['values']['field']) && empty($form_state['values']['permissions']) && empty($form_state['values']['node_author'])) { // this isn't a great validation check because it doesn't allow for setting errors on each form item // but what is there really to validate here? $errors['node_privacy_byrole'] = t('Please use the supplied form to submit access permissions.'); } foreach ($errors as $name => $message) { form_set_error($name, $message); } return count($errors) == 0; } /** * Submission hook for saving action settings. */ function node_privacy_byrole_rolereference_action_submit($form, &$form_state) { $params = array( 'node_privacy_byrole' => $form_state['values']['node_privacy_byrole'], 'field' => $form_state['values']['field'], 'permissions' => $form_state['values']['permissions'], 'node_author' => $form_state['values']['node_author'], ); return $params; } /** * Loop recursively through form submissions to find values of rids since we * can't be sure if the field returns a string or a nested array like in a multiple select. */ function _node_privacy_byrole_walk_role_fields($form) { if (!is_array($form)) { return array($form); } else { $rids = array(); foreach (array_keys($form) as $getvar) { if (element_child($getvar) && is_array($form) && !is_null($form[$getvar])) { $returned = _node_privacy_byrole_walk_role_fields($form[$getvar]); $rids = array_merge($rids, $returned); } else { $rids[] = $getvar; } } } return $rids; }