From e364752b608aeb2c709ce24d00573788a590e056 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde Date: Tue, 9 Jul 2013 15:31:42 +0200 Subject: [PATCH] Issue #1976696 by chaby, kristiaanvandeneynde: Polyfill permissions for the Node module. --- group.install | 2 + group.module | 7 +- implementations/group.node.inc | 336 ++++++++++++++++++++++++++++++++++++++++ misc/group.js | 17 ++ 4 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 implementations/group.node.inc create mode 100644 misc/group.js diff --git a/group.install b/group.install index e6853e5..506fa7e 100644 --- a/group.install +++ b/group.install @@ -10,6 +10,8 @@ function group_install() { variable_set('group_admin_theme', '1'); variable_set('group_autocomplete_suffix', array('general' => 0)); + + node_access_needs_rebuild(TRUE); } /** diff --git a/group.module b/group.module index e40aee8..8b2823e 100644 --- a/group.module +++ b/group.module @@ -14,6 +14,11 @@ module_load_include('inc', 'group', 'helpers/entity.group_type'); module_load_include('inc', 'group', 'helpers/group'); /** + * Load module implementations without polluting the .module file. + */ +module_load_include('inc', 'group', 'implementations/group.node'); + +/** * Implements hook_hook_info(). * * Makes sure this module automatically finds exported Group @@ -493,7 +498,7 @@ function group_group_permission() { $permissions = array( 'administer group' => array( 'title' => t('Administer group'), - 'description' => t('Administer the group, its users and permissions'), + 'description' => t('Administer the group, its content and members'), 'restrict access' => TRUE, ), 'view group' => array( diff --git a/implementations/group.node.inc b/implementations/group.node.inc new file mode 100644 index 0000000..74ef0f7 --- /dev/null +++ b/implementations/group.node.inc @@ -0,0 +1,336 @@ + $node_type->name); + + $permissions["view $node_type->type"] = array( + 'title' => t('%node_type: View content', $replace), + ); + $permissions["create $node_type->type"] = array( + 'title' => t('%node_type: Create new content', $replace), + ); + $permissions["edit own $node_type->type content"] = array( + 'title' => t('%node_type: Edit own content', $replace), + ); + $permissions["edit any $node_type->type content"] = array( + 'title' => t('%node_type: Edit any content', $replace), + ); + $permissions["delete own $node_type->type content"] = array( + 'title' => t('%node_type: Delete own content', $replace), + ); + $permissions["delete any $node_type->type content"] = array( + 'title' => t('%node_type: Delete any content', $replace), + ); + } + + return $permissions; +} + +/** + * Implements hook_node_access(). + * + * We rely on the grant system to allow access to a node. Only node creation + * cannot be handled by the grant system and is therefore taken care of here. + * + * @see group_node_grants(). + * @see group_node_access_records(). + */ +function group_node_access($node, $op, $account) { + if (is_string($node)) { + // Check for creation rights in the user's groups. + if ($op == 'create' && isset($account->uid)) { + foreach (group_load_by_member($account->uid) as $group) { + if (group_access('administer group', $group, $account) + || group_access("create $node", $group, $account)) { + return NODE_ACCESS_ALLOW; + } + } + } + } + + return NODE_ACCESS_IGNORE; +} + +/** + * Implements hook_node_grants(). + * + * @see group_node_access_records(). + */ +function group_node_grants($account, $op) { + $grants = array(); + + // If the user can bypass group access, he only needs the master grant. + if (user_access('bypass group access', $account)) { + return array('group:bypass' => array(GROUP_BYPASS_GRANT_ID)); + } + + $node_types = node_type_get_types(); + foreach (group_load_by_member($account->uid) as $group) { + // If the user is an admin, he requires no further specific grants. + if (group_access('administer group', $group, $account)) { + $grants['group:administer'][] = $group->gid; + continue; + } + + foreach ($node_types as $node_type) { + // Allow the user to view content of this type. + if ($op == 'view' && group_access("view $node_type->type", $group, $account)) { + $grants["group:$node_type->type:view"][] = $group->gid; + } + // Allow the user to edit any content of this type. + elseif ($op == 'update' && group_access("edit any $node_type->type", $group, $account)) { + $grants["group:$node_type->type:update"][] = $group->gid; + } + // Allow the user to delete any content of this type. + elseif ($op == 'delete' && group_access("delete any $node_type->type", $group, $account)) { + $grants["group:$node_type->type:delete"][] = $group->gid; + } + // Allow the user to edit own content of this type. + elseif ($op == 'update' && group_access("edit own $node_type->type", $group, $account)) { + $grants["group:$group->gid:$node_type->type:update"][] = $account->uid; + } + // Allow the user to delete own content of this type. + elseif ($op == 'delete' && group_access("delete own $node_type->type", $group, $account)) { + $grants["group:$group->gid:$node_type->type:delete"][] = $account->uid; + } + } + } + + return $grants; +} + +/** + * Implements hook_node_access_records(). + * + * We define the following realms: + * - 'group:bypass': Grants full access to any user having the + * 'bypass group access' permission. Because a realm id isn't really + * needed here, we specify the module author's date of birth: 1986. + * - 'group:administer': Grants full access to any user having the + * 'administer group' permission for the group the node belongs to. Because + * this realm is defined for every group, we use the group id as realm id. + * - 'group:NODE_TYPE:OP': Grants access for the specified operation to any + * user having one of the following group permissions: + * - view NODE_TYPE + * - update any NODE_TYPE + * - delete any NODE_TYPE + * Because these realms are defined for every group, we use the group id as + * realm id. + * - 'group:GROUP_ID:NODE_TYPE:OP': Grants access for the specified operation + * to any user having one of the following group permissions: + * - update own NODE_TYPE + * - delete own NODE_TYPE + * Because these realms are defined for specific group-user combinations, we + * should use the user id as the grant id. However, we still need to know + * what group the grant is for, so we incorporate the group id into the + * realm name. We chose to incorporate the group id instead of the user id + * because there will almost always be more users than groups. + * + * @see group_node_grants(). + */ +function group_node_access_records($node) { + $grants = array(); + + // Add realms specific to this group. + if (!empty($node->group)) { + // Add the realm corresponding to the 'bypass group access' permission. + $grants[] = array( + 'realm' => 'group:bypass', + 'gid' => GROUP_BYPASS_GRANT_ID, + 'grant_view' => 1, + 'grant_update' => 1, + 'grant_delete' => 1, + 'priority' => 0, + ); + + // Add the realm corresponding to the 'administer group' permission. + $grants[] = array( + 'realm' => 'group:administer', + 'gid' => $node->group, + 'grant_view' => 1, + 'grant_update' => 1, + 'grant_delete' => 1, + 'priority' => 0, + ); + + // Add realms for published nodes. + if ($node->status) { + $defaults_any = array( + 'gid' => $node->group, + 'grant_view' => 0, + 'grant_update' => 0, + 'grant_delete' => 0, + 'priority' => 0, + ); + + $defaults_own = array( + 'gid' => $node->uid, + ) + $defaults_any; + + // View any content of this type. + $grants[] = array ( + 'realm' => "group:$node->type:view", + 'grant_view' => 1, + ) + $defaults_any; + + // Edit any content of this type. + $grants[] = array ( + 'realm' => "group:$node->type:update", + 'grant_update' => 1, + ) + $defaults_any; + + // Delete any content of this type. + $grants[] = array ( + 'realm' => "group:$node->type:delete", + 'grant_delete' => 1, + ) + $defaults_any; + + // Edit own content of this type. + $grants[] = array ( + 'realm' => "group:$node->group:$node->type:update", + 'grant_update' => 1, + ) + $defaults_own; + + // Delete own content of this type. + $grants[] = array ( + 'realm' => "group:$node->group:$node->type:delete", + 'grant_delete' => 1, + ) + $defaults_own; + } + } + + return $grants; +} + +/** + * Implements hook_node_prepare(). + */ +function group_node_prepare($node) { + if (empty($node->group)) { + $groups = isset($node->nid) ? group_get_entity_groups('node', $node->nid) : array(); + + // Nodes can only have one group. + $node->group = !empty($groups) ? key($groups) : 0; + } +} + +/** + * Implements hook_node_insert(). + */ +function group_node_insert($node) { + group_node_save($node); +} + +/** + * Implements hook_node_update(). + */ +function group_node_update($node) { + // Delete all existing links. + $query = db_delete('group_entity'); + $query->condition('entity_id', $node->nid); + $query->condition('entity_type', 'node'); + $query->execute(); + + // Saving takes care of creating new links. + group_node_save($node); +} + +/** + * Helper for hook_node_insert() and hook_node_update(). + */ +function group_node_save($node) { + if (isset($node->group)) { + group_load($node->group)->addEntity($node->nid, 'node', $node->type); + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter(). + * + * Adds a Group vertical tab to the node form. + * + * @see group_node_submit() + */ +function group_form_node_form_alter(&$form, $form_state) { + global $user; + $node_type = $form['#node']->type; + + $user_groups = array(); + foreach (group_load_by_member($user->uid) as $group) { + // You can only select those groups that you can create nodes of this type + // in. It would not make sense if someone could move nodes to a group where + // he does not have creation rights. + if ($group->userHasPermission($user->uid, "create $node_type") + || $group->userHasPermission($user->uid, 'administer group')) { + $user_groups[$group->gid] = check_plain($group->title); + } + } + + $form['group_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Group settings'), + '#access' => !empty($user_groups), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#group' => 'additional_settings', + '#attributes' => array( + 'class' => array('node-form-group-information'), + ), + '#attached' => array( + 'js' => array(drupal_get_path('module', 'group') . '/misc/group.js'), + ), + '#tree' => TRUE, + '#weight' => -50, + ); + + $form['group_settings']['group'] = array( + '#type' => 'select', + '#title' => t('Select a group to attach this node to.'), + '#description' => t("By selecting a group, the node will inherit the group's access control."), + '#options' => $user_groups, + '#empty_option' => t('- No group -'), + '#empty_value' => 0, + '#default_value' => $form['#node']->group, + // If the user can't create this node outside of a group, he is not allowed + // to move it to the sitewide scope either. + '#required' => !user_access("create $node_type"), + ); +} + +/** + * Implements hook_node_submit(). + * + * @see group_form_node_form_alter() + */ +function group_node_submit($node, $form, $form_state) { + // Decompose the selected menu parent option into 'menu_name' and 'plid', if + // the form used the default parent selection widget. + if (!empty($form_state['values']['group_settings']['group'])) { + $node->group = $form_state['values']['group_settings']['group']; + } +} diff --git a/misc/group.js b/misc/group.js new file mode 100644 index 0000000..8551a76 --- /dev/null +++ b/misc/group.js @@ -0,0 +1,17 @@ +(function ($) { + +Drupal.behaviors.groupFieldsetSummaries = { + attach: function (context) { + $('fieldset.node-form-group-information', context).drupalSetSummary(function (context) { + var option = $('.form-item-group-settings-group option:selected', context); + + if (option.val() != '0') { + return $.trim(option.text()); + } + + return Drupal.t('Not a group node'); + }); + } +}; + +})(jQuery); -- 1.7.9.4