diff --git a/modules/blog/blog.module b/modules/blog/blog.module index af72d54f7e29a76f317c59fede3d560e69ffe736..9ca7ff9af90f2b53e8bc5c2debe290b93c963c7a 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -19,28 +19,6 @@ function blog_node_info() { ); } -/** - * Implement hook_permission(). - */ -function blog_permission() { - return node_list_permissions('blog'); -} - -/** - * Implement hook_access(). - */ -function blog_access($op, $node, $account) { - switch ($op) { - case 'create': - // Anonymous users cannot post even if they have the permission. - return user_access('create blog content', $account) && $account->uid; - case 'update': - return user_access('edit any blog content', $account) || (user_access('edit own blog content', $account) && ($node->uid == $account->uid)); - case 'delete': - return user_access('delete any blog content', $account) || (user_access('delete own blog content', $account) && ($node->uid == $account->uid)); - } -} - /** * Implement hook_user_view(). */ diff --git a/modules/forum/forum.module b/modules/forum/forum.module index 9737e881ea0d91c6b3040c02c9470c18dbdd249d..29a6289b4ff10a23773a64e233d1078bdd2a411e 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -404,20 +404,6 @@ function forum_node_info() { ); } -/** - * Implement hook_access(). - */ -function forum_access($op, $node, $account) { - switch ($op) { - case 'create': - return user_access('create forum content', $account); - case 'update': - return user_access('edit any forum content', $account) || (user_access('edit own forum content', $account) && ($account->uid == $node->uid)); - case 'delete': - return user_access('delete any forum content', $account) || (user_access('delete own forum content', $account) && ($account->uid == $node->uid)); - } -} - /** * Implement hook_permission(). */ @@ -428,7 +414,6 @@ function forum_permission() { 'description' => t('Manage forums and configure forum administration settings.'), ), ); - $perms += node_list_permissions('forum'); return $perms; } diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 1fc8d6d3a211e6c15d2b5763a14a5bb84be7db51..2aa02c320ed28bb9a0e091bfe2cc39f147c8d112 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -341,6 +341,67 @@ function hook_node_load($nodes, $types) { } } +/** + * Control access to a node. + * + * Modules may implement this hook if they want to have a say in whether or not + * a given user has access to perform a given operation on a node. + * + * The administrative account (user ID #1) always passes any access check, + * so this hook is not called in that case. Users with the "bypass node access" + * permission may always view and edit content through the administrative + * interface. + * + * Note that not all modules will want to influence access on all + * node types. If your module does not want to actively grant or + * block access, return NODE_ACCESS_IGNORE or simply return nothing. + * Blindly returning FALSE will break other node access modules. + * + * @link http://api.drupal.org/api/group/node_access/7 More on the node access system @endlink + * @ingroup node_access + * @param $node + * The node on which the operation is to be performed, or, if it does + * not yet exist, the type of node to be created. + * @param $op + * The operation to be performed. Possible values: + * - "create" + * - "delete" + * - "update" + * - "view" + * @param $account + * A user object representing the user for whom the operation is to be + * performed. + * @return + * NODE_ACCESS_ALLOW if the operation is to be allowed; + * NODE_ACCESS_DENY if the operation is to be denied; + * NODE_ACCESSS_IGNORE to not affect this operation at all. + */ +function hook_node_access($node, $op, $account) { + $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); + + if (in_array($type, nodeperms_get_configured_types())) { + if ($op == 'create' && user_access('create ' . $type . ' content', $account)) { + return NODE_ACCESS_ALLOW; + } + + if ($op == 'update') { + if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { + return NODE_ACCESS_ALLOW; + } + } + + if ($op == 'delete') { + if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { + return NODE_ACCESS_ALLOW; + } + } + } + + // Returning nothing from this function would have the same effect. + return NODE_ACCESS_IGNORE; +} + + /** * The node is about to be shown on the add/edit form. * @@ -664,54 +725,6 @@ function hook_node_type_delete($info) { variable_del('comment_' . $info->type); } -/** - * Define access restrictions. - * - * This hook allows node modules to limit access to the node types they - * define. - * - * @param $op - * The operation to be performed. Possible values: - * - "create" - * - "delete" - * - "update" - * - "view" - * @param $node - * The node on which the operation is to be performed, or, if it does - * not yet exist, the type of node to be created. - * @param $account - * A user object representing the user for whom the operation is to be - * performed. - * @return - * TRUE if the operation is to be allowed; - * FALSE if the operation is to be denied; - * NULL to not override the settings in the node_access table, or access - * control modules. - * - * The administrative account (user ID #1) always passes any access check, - * so this hook is not called in that case. If this hook is not defined for - * a node type, all access checks will fail, so only the administrator will - * be able to see content of that type. However, users with the "administer - * nodes" permission may always view and edit content through the - * administrative interface. - * @see http://api.drupal.org/api/group/node_access/7 - * - * For a detailed usage example, see node_example.module. - * - * @ingroup node_access - */ -function hook_access($op, $node, $account) { - if ($op == 'create') { - return user_access('create stories', $account); - } - - if ($op == 'update' || $op == 'delete') { - if (user_access('edit own stories', $account) && ($account->uid == $node->uid)) { - return TRUE; - } - } -} - /** * Respond to node deletion. * @@ -929,11 +942,11 @@ function hook_validate($node, &$form) { * @return * $node. The passed $node parameter should be modified as necessary and * returned so it can be properly presented. Nodes are prepared for display - * by assembling a structured array, formatted as in the Form API, in - * $node->content. As with Form API arrays, the #weight property can be + * by assembling a structured array, formatted as in the Form API, in + * $node->content. As with Form API arrays, the #weight property can be * used to control the relative positions of added elements. After this * hook is invoked, node_build() calls field_attach_view() to add field - * views to $node->content, and then invokes hook_node_view() and + * views to $node->content, and then invokes hook_node_view() and * hook_node_build_alter(), so if you want to affect the final * view of the node, you might consider implementing one of these hooks * instead. diff --git a/modules/node/node.module b/modules/node/node.module index 94373e834797cebb9d693c77bd182533fcece3ac..1320a9d053260811d7f351f63d6dad5ce8793bd9 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -15,6 +15,21 @@ */ define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60); +/** + * Modules should return this value from hook_node_access() to allow access to a node. + */ +define('NODE_ACCESS_ALLOW', 'allow'); + +/** + * Modules should return this value from hook_node_access() to deny access to a node. + */ +define('NODE_ACCESS_DENY', 'deny'); + +/** + * Modules should return this value from hook_node_access() to not affect node access. + */ +define('NODE_ACCESS_IGNORE', NULL); + /** * Implement hook_help(). */ @@ -1337,10 +1352,9 @@ function node_permission() { ), ); - foreach (node_type_get_types() as $type) { - if ($type->base == 'node_content') { - $perms += node_list_permissions($type); - } + // Generate standard node permissions for all applicable node types. + foreach (node_permissions_get_configured_types() as $type) { + $perms += node_list_permissions($type); } return $perms; @@ -2222,27 +2236,35 @@ function node_search_validate($form, &$form_state) { * * In determining access rights for a node, node_access() first checks * whether the user has the "bypass node access" permission. Such users have - * unrestricted access to all nodes. Then the node module's hook_access() - * is called, and a TRUE or FALSE return value will grant or deny access. - * This allows, for example, the blog module to always grant access to the - * blog author, and for the book module to always deny editing access to - * PHP pages. - * - * If node module does not intervene (returns NULL), then the - * node_access table is used to determine access. All node access - * modules are queried using hook_node_grants() to assemble a list of - * "grant IDs" for the user. This list is compared against the table. - * If any row contains the node ID in question (or 0, which stands for "all - * nodes"), one of the grant IDs returned, and a value of TRUE for the - * operation in question, then access is granted. Note that this table is a - * list of grants; any matching row is sufficient to grant access to the - * node. + * unrestricted access to all nodes. user 1 will always pass this check. + * + * Next, all implementations of hook_node_access() will be called. Each + * implementation may explicitly allow, explicitly deny, or ignore the access + * request. If at least one module says to deny the request, it will be rejected. + * If no modules deny the request and at least one says to allow it, the request + * will be permitted. + * + * If all modules ignore the access request, then the node_access table is used + * to determine access. All node access modules are queried using + * hook_node_grants() to assemble a list of "grant IDs" for the user. This list + * is compared against the table. If any row contains the node ID in question + * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a + * value of TRUE for the operation in question, then access is granted. Note + * that this table is a list of grants; any matching row is sufficient to + * grant access to the node. * * In node listings, the process above is followed except that - * hook_access() is not called on each node for performance reasons and for + * hook_node_access() is not called on each node for performance reasons and for * proper functioning of the pager system. When adding a node listing to your - * module, be sure to use db_rewrite_sql() to add - * the appropriate clauses to your query for access checks. + * module, be sure to use a dynamic query created by db_select() and add a tag + * of "node_access" to ensure that only nodes to which the user has access + * are retrieved. + * + * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() + * will block access to the node. Therefore, implementers should take care to + * not deny access unless they really intend to. Unless a module wishes to + * actively deny access it should return NODE_ACCESS_IGNORE (or simply return + * nothing) to allow other modules or the node_access table to control access. * * To see how to write a node access module of your own, see * node_access_example.module. @@ -2292,12 +2314,17 @@ function node_access($op, $node, $account = NULL) { return FALSE; } - // Can't use node_invoke('access', $node), because the access hook takes the - // $op parameter before the $node parameter. - $base = node_type_get_base($node); - $access = module_invoke($base, 'access', $op, $node, $account); - if (!is_null($access)) { - return $access; + // We grant access to the node if both of the following conditions are met: + // - No modules say to deny access. + // - At least one module says to grant access. + // If no module specified either allow or deny, we fall back to the + // node_access table. + $access = module_invoke_all('node_access', $node, $op, $account); + if (in_array(NODE_ACCESS_DENY, $access, TRUE)) { + return FALSE; + } + elseif (in_array(NODE_ACCESS_ALLOW, $access, TRUE)) { + return TRUE; } // Check if authors can view their own unpublished nodes. @@ -2337,6 +2364,99 @@ function node_access($op, $node, $account = NULL) { return FALSE; } +/** + * Implement hook_node_access(). + */ +function node_node_access($node, $op, $account) { + $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); + + if (in_array($type, node_permissions_get_configured_types())) { + if ($op == 'create' && user_access('create ' . $type . ' content', $account)) { + return NODE_ACCESS_ALLOW; + } + + if ($op == 'update') { + if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { + return NODE_ACCESS_ALLOW; + } + } + + if ($op == 'delete') { + if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { + return NODE_ACCESS_ALLOW; + } + } + } + + return NODE_ACCESS_IGNORE; +} + +/** + * Helper function to generate standard node permission list for a given type. + * + * @param $type + * The machine-readable name of the node type. + * @return array + * An array of permission names and descriptions. + */ +function node_list_permissions($type) { + $info = node_type_get_type($type); + $type = check_plain($info->type); + + // Build standard list of node permissions for this type. + $perms = array( + "create $type content" => array( + 'title' => t('Create %type_name content', array('%type_name' => $info->name)), + 'description' => t('Create new %type_name content.', array('%type_name' => $info->name)), + ), + "edit own $type content" => array( + 'title' => t('Edit own %type_name content', array('%type_name' => $info->name)), + 'description' => t('Edit %type_name content created by the user.', array('%type_name' => $info->name)), + ), + "edit any $type content" => array( + 'title' => t('Edit any %type_name content', array('%type_name' => $info->name)), + 'description' => t('Edit any %type_name content, regardless of its author.', array('%type_name' => $info->name)), + ), + "delete own $type content" => array( + 'title' => t('Delete own %type_name content', array('%type_name' => $info->name)), + 'description' => t('Delete %type_name content created by the user.', array('%type_name' => $info->name)), + ), + "delete any $type content" => array( + 'title' => t('Delete any %type_name content', array('%type_name' => $info->name)), + 'description' => t('Delete any %type_name content, regardless of its author.', array('%type_name' => $info->name)), + ), + ); + + return $perms; +} + +/** + * Returns an array of node types that should be managed by permissions. + * + * By default, this will include all node types in the system. To exclude a + * specific node from getting permissions defined for it, set the + * node_permissions_$type variable to 0. Core does not provide an interface + * for doing so, however, contrib modules may exclude their own nodes in + * hook_install(). Alternatively, contrib modules may configure all node types + * at once, or decide to apply some other hook_node_access() implementation + * to some or all node types. + * + * @return + * An array of node types managed by this module. + */ +function node_permissions_get_configured_types() { + + $configured_types = array(); + + foreach (node_type_get_types() as $type => $info) { + if (variable_get('node_permissions_' . $type, 1)) { + $configured_types[] = $type; + } + } + + return $configured_types; +} + /** * Generate an SQL join clause for use in fetching a node listing. * @@ -2762,31 +2882,6 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { * @{ */ -/** - * Implement hook_access(). - * - * Named so as not to conflict with node_access() - */ -function node_content_access($op, $node, $account) { - $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); - - if ($op == 'create') { - return user_access('create ' . $type . ' content', $account); - } - - if ($op == 'update') { - if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { - return TRUE; - } - } - - if ($op == 'delete') { - if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) { - return TRUE; - } - } -} - /** * Implement hook_form(). */ @@ -3120,45 +3215,6 @@ function node_unpublish_by_keyword_action($node, $context) { } } -/** - * Helper function to generate standard node permission list for a given type. - * - * @param $type - * The machine-readable name of the node type. - * @return array - * An array of permission names and descriptions. - */ -function node_list_permissions($type) { - $info = node_type_get_type($type); - $type = check_plain($info->type); - - // Build standard list of node permissions for this type. - $perms = array( - "create $type content" => array( - 'title' => t('Create %type_name content', array('%type_name' => $info->name)), - 'description' => t('Create new %type_name content.', array('%type_name' => $info->name)), - ), - "edit own $type content" => array( - 'title' => t('Edit own %type_name content', array('%type_name' => $info->name)), - 'description' => t('Edit %type_name content created by the user.', array('%type_name' => $info->name)), - ), - "edit any $type content" => array( - 'title' => t('Edit any %type_name content', array('%type_name' => $info->name)), - 'description' => t('Edit any %type_name content, regardless of its author.', array('%type_name' => $info->name)), - ), - "delete own $type content" => array( - 'title' => t('Delete own %type_name content', array('%type_name' => $info->name)), - 'description' => t('Delete %type_name content created by the user.', array('%type_name' => $info->name)), - ), - "delete any $type content" => array( - 'title' => t('Delete any %type_name content', array('%type_name' => $info->name)), - 'description' => t('Delete any %type_name content, regardless of its author.', array('%type_name' => $info->name)), - ), - ); - - return $perms; -} - /** * Implement hook_requirements(). */ diff --git a/modules/poll/poll.module b/modules/poll/poll.module index bcd5d62b37bb9d6b9ab496afee6a46b969ebab2f..6c044b558856443f9bd89e8ea912d6d950d0ecea 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -54,8 +54,7 @@ function poll_theme() { * Implement hook_permission(). */ function poll_permission() { - $perms = node_list_permissions('poll'); - $perms += array( + $perms = array( 'vote on polls' => array( 'title' => t('Vote on polls'), 'description' => t('Cast votes on polls.'), @@ -73,20 +72,6 @@ function poll_permission() { return $perms; } -/** - * Implement hook_access(). - */ -function poll_access($op, $node, $account) { - switch ($op) { - case 'create': - return user_access('create poll content', $account); - case 'update': - return user_access('edit any poll content', $account) || (user_access('edit own poll content', $account) && ($node->uid == $account->uid)); - case 'delete': - return user_access('delete any poll content', $account) || (user_access('delete own poll content', $account) && ($node->uid == $account->uid)); - } -} - /** * Implement hook_menu(). */