--- node.module.5.7.orig 2008-04-16 23:37:39.000000000 -0700 +++ node.module 2008-05-09 00:34:33.000000000 -0700 @@ -1120,6 +1120,16 @@ function node_menu($may_cache) { 'callback arguments' => array('node_configure_rebuild_confirm'), 'access' => user_access('administer nodes'), 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'admin/content/multinode', + 'title' => t('Multinode user interface'), + 'description' => t('Interface for configuring multiple node access.'), + 'callback' => 'drupal_get_form', + 'callback arguments' => array('node_multinode'), + 'access' => user_access('administer nodes'), + 'type' => MENU_NORMAL_ITEM + ); $items[] = array( 'path' => 'admin/content/types', @@ -2731,22 +2741,19 @@ function node_access($op, $node = NULL) // If the module did not override the access rights, use those set in the // node_access table. - if ($op != 'create' && $node->nid && $node->status) { - $grants = array(); - foreach (node_access_grants($op) as $realm => $gids) { - foreach ($gids as $gid) { - $grants[] = "(gid = $gid AND realm = '$realm')"; - } - } - - $grants_sql = ''; - if (count($grants)) { - $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; + if ($op != 'create' && $node->nid) { + $grants_sql = node_access_grants_sql($op, NULL, $user->uid, $node->status); + // If the return value is FALSE, then the node status is unpublished and + // none of the grants requested an access check be run. In which case, + // we should fall through to the final 'if' statement. + if ($grants_sql !== FALSE) { + if (!empty($grants_sql)) { + $grants_sql .= ' AND'; + } + $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql grant_$op >= 1"; + $result = db_query($sql, $node->nid); + return (db_result($result)); } - - $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1"; - $result = db_query($sql, $node->nid); - return (db_result($result)); } // Let authors view their own nodes. @@ -2793,17 +2800,7 @@ function _node_access_where_sql($op = 'v return; } - $grants = array(); - foreach (node_access_grants($op, $uid) as $realm => $gids) { - foreach ($gids as $gid) { - $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')"; - } - } - - $grants_sql = ''; - if (count($grants)) { - $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; - } + $grants_sql = node_access_grants_sql('view', $node_access_alias, $uid); $sql = "$node_access_alias.grant_$op >= 1 $grants_sql"; return $sql; @@ -2833,7 +2830,6 @@ function node_access_grants($op, $uid = else { $user_object = $user; } - return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op)); } @@ -2844,18 +2840,7 @@ function node_access_view_all_nodes() { static $access; if (!isset($access)) { - $grants = array(); - foreach (node_access_grants('view') as $realm => $gids) { - foreach ($gids as $gid) { - $grants[] = "(gid = $gid AND realm = '$realm')"; - } - } - - $grants_sql = ''; - if (count($grants)) { - $grants_sql = 'AND ('. implode(' OR ', $grants) .')'; - } - + $grants_sql = node_access_grants_sql('view'); $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1"; $result = db_query($sql); $access = db_result($result); @@ -2865,6 +2850,160 @@ function node_access_view_all_nodes() { } /** + * Check the logic of node grants and prepare the sql statement. + * + * @param $op + * The operation that the user is trying to perform. + * @param $uid + * The user ID performing the operation. If omitted, the current user is used. + * @param $status + * The publication status of the node being checked. Typically, node_access + * checks are not run for unpublished nodes. However, some advanced uses + * require that users can act on unpublished nodes. + * @return + * There are three possible return values. + * - A sql string containing the appropriate WHERE clauses, grouped together + * according to the logic of hook_node_grants. + * - An empty string, indicating that no extra rules are needed. + * - Boolean FALSE, indicating that all nodes are unpublished and no module + * has explicitly requested an access check. + */ +function node_access_grants_sql($op = 'view', $node_access_alias = NULL, $uid = NULL, $status = TRUE) { + // Define the grants array. + $grants = array(); + $template = array(); + // First, acquire all the grants as an array of rules. + $rules = node_access_grants($op, $uid); + + // Get whatever is stored in multinode table. + // Add to rules + if (db_table_exists('multinode_access')) { + $result = db_query("SELECT * FROM {multinode_access}"); + while ($row = db_fetch_object($result)) { + foreach ($rules as $realm => $rule_grants) { + if ($row->realm == $realm) { + $rules[$realm]['group'] = $row->groupname; + $rules[$realm]['logic'] = $row->logic; + $rules[$realm]['weight'] = $row->weight; + $rules[$realm]['check'] = $row->checkstatus; + } + } + } + } + + // Prepare the table alias, if needed. + if (!empty($node_access_alias)) { + $alias = $node_access_alias .'.'; + } + // It may be that only the default rule is active. In that case, we can skip the + // process below. + if (count($rules) == 1 && key($rules) == 'all') { + $grants_sql .= "AND (". $alias ."realm = 'all' AND ". $alias ."gid = 0)"; + } + else { + // Process the grants into logical groupings. + foreach ($rules as $realm => $rule) { + // Set the status flag. + $status += $rule['check']; + // Set the default clause group. Then check for options. + $group = 'all'; + if (!empty($rule['group'])) { + $group = $rule['group']; + $template[$group]['name'] = $rule['group']; + } + if (!empty($rule['logic'])) { + $template[$group]['logic'] = $rule['logic']; + } + if (!empty($rule['weight'])) { + $template[$group]['weight'] = $rule['weight']; + } + foreach ($rule as $key => $gid) { + // Grants ids must be numeric. + if (is_numeric($key)) { + $grants[$group][$realm][] = $gid; + } + } + } + // In most cases, node status must be set to TRUE. However, there are use-cases where + // we allow access to unpublished nodes. So we check the status to see if we need to + // continue with the logic or end this IF statement. + if (!$status) { + return FALSE; + } + + $grants_sql = ''; + $subquery = array(); + $subqueries = 'no'; + + // Run throught the $grants, if needed and generate the query SQL. + if (count($grants)) { + foreach ($grants as $group => $grant) { + $clauses = array(); + foreach ($grant as $key => $value) { + foreach ($value as $gid) { + $clauses[] = "(". $alias ."realm = '$key' AND ". $alias ."gid = $gid)"; + } + } + if ($group == 'all') { + $grants_sql .= 'AND ('. implode(' OR ', $clauses) .') '; + } + else { + // Required elements must go into a subquery. Will not work prior to MySQL 4.1.x. + // Set defaults. + $subqueries = 'yes'; + $weight = 0; + $logic = 'AND'; + + if ($template[$group]['weight']) { + $weight = $template[$group]['weight']; + } + + if ($template[$group]['logic']) { + $logic = $template[$group]['logic']; + } + + if ($logic == 'OR') { + $this_query = "OR ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE "; + } else { + $this_query = "AND ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE "; + } + $this_query = $this_query .'('. implode(' OR ', $clauses) .')) '; + + $subquery[$this_query] = $weight; + } + } + if ($subqueries == 'yes') { + // Get subqueries from subquery table; + // Sort if first by weight. + $subquery_phrase = ''; + asort($subquery); + foreach ($subquery as $query=>$weight) { + $subquery_phrase .= $query; + } +// +// Just in case I need to put parenthesis elsewhere +// +// $pattern = '/^AND /'; +// $replacement = 'AND ('; +// $subquery_phrase = preg_replace($pattern, $replacement, $subquery_phrase); +// $pattern = '/^OR /'; +// $replacement = 'OR ('; +// $subquery_phrase = preg_replace($pattern, $replacement, $subquery_phrase); +// $subquery_phrase = $subquery_phrase . ')'; + // Need to add parenthesis after first OR or AND and at the end of the query. + $grants_sql .= $subquery_phrase; + // Place everything after "AND" within parenthesis + $pattern = '/^AND /'; + $replacement = 'AND ('; + $grants_sql = preg_replace($pattern, $replacement, $grants_sql); + $grants_sql = $grants_sql . ')'; + } + } + } + return $grants_sql; +} + +/** * Implementation of hook_db_rewrite_sql */ function node_db_rewrite_sql($query, $primary_table, $primary_field) { @@ -3050,3 +3189,100 @@ function node_forms() { } return $forms; } + +//////////////////////////////// +////// Multinode Access //////// +//////////////////////////////// + +/** + * Generate multinode access UI form. + */ +function node_multinode($theme = NULL) { + global $theme_key, $custom_theme, $user; + $uid = $user->uid; + + // Get whatever is stored in multinode table. + $existing = array(); + $result = db_query("SELECT * FROM {multinode_access}"); + while ($row = db_fetch_object($result)) { + $existing[$row->realm] = $row; + } + + // Get all rule realms from node_access table. + $result = db_query("SELECT DISTINCT realm FROM {node_access}"); + while ($row = db_fetch_object($result)) { + $rules[] = $row->realm; + } + + // Get all rule realms from modules. User doing this should have grants in all modules affected. + $op = 'view'; + $mrules = node_access_grants($op, $uid); + foreach ($mrules as $realm => $grants) { + $rules[] = $realm; + } + + // Get only unique values + $rules = array_unique($rules); + + // Sort the rule realms + asort($rules); + + $form['#tree'] = TRUE; + foreach ($rules as $realm) { + $form[$realm]['checkbox'] = array('#type' => 'checkbox', '#default_value' => (isset($existing[$realm]->realm) ? 1 : 0)); + $form[$realm]['realm'] = array('#type' => 'textfield', '#size' => 25, '#disabled' => TRUE, '#value' => check_plain($realm), '#default_value' => $realm); + $form[$realm]['group'] = array('#type' => 'textfield', '#size' => 5, '#default_value' => (isset($existing[$realm]->groupname) ? $existing[$realm]->groupname : '') ); + $form[$realm]['logic'] = array('#type' => 'select', '#default_value' => (isset($existing[$realm]->logic) ? $existing[$realm]->logic : 'AND'), '#options' => array('AND' => 'AND', 'OR' => 'OR') ); + $form[$realm]['weight'] = array('#type' => 'select', '#default_value' => (isset($existing[$realm]->weight) ? $existing[$realm]->weight : '0'), '#options' => array('0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9') ); + $form[$realm]['check'] = array('#type' => 'select', '#default_value' => (isset($existing[$realm]->checkstatus) ? $existing[$realm]->checkstatus : '0'), '#options' => array('0' => '0', '1' => '1') ); + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save changes')); + + return $form; + +} + +/** + * Theme function to render the table for the node_multinode + * Multinode access UI + */ +function theme_node_multinode($form) { + $output .= "\n