--- 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
\n"; + $output .= '
'. t('Here you can configure multinode access. Normal node access is NOT affected unless you CHECK items here. To reverse any changes made here, simply UNCHECK all items here and Save changes (otherwise, multinode access will continue).') ."
\n"; + $header = array(t(''), t('Realm'), t('Group'), t('Logic'), t('Weight'), t('Check')); + $rows = array(); + + foreach (element_children($form) as $i) { + $block = &$form[$i]; + $rows[] = array(drupal_render($block['checkbox']), drupal_render($block['realm']), + drupal_render($block['group']), drupal_render($block['logic']), + drupal_render($block['weight']), drupal_render($block['check']), + ); + } + + $output .= theme('table', $header, $rows, array('id' => 'og-roles-multinode-table')); + + $output .= drupal_render($form['submit']); + + $output .= "
\n"; + $output .= drupal_render($form); + return $output; +} + +/** + * Process multinode form submission. + */ +function node_multinode_submit($form_id, $form_values) { + // Erase all existing settings + db_query ("DELETE FROM {multinode_access}"); + + // Insert new settings + foreach ($form_values as $block) { + if ($block['checkbox'] == 1) { + db_query("INSERT INTO {multinode_access} (realm, groupname, logic, weight, checkstatus) VALUES ('%s','%s','%s','%s',%d)", $block['realm'], $block['group'], $block['logic'], $block['weight'], $block['check']); + } + } + drupal_set_message(t('The multinode access settings have been updated.')); + cache_clear_all(); +}