domain.module

// $Id: domain.module,v 1.40.2.17 2008/07/25 18:07:14 agentken Exp $

/**
 * @defgroup domain Domain Access: A subdomain-based access control system
 *
 * The core Domain Access module.
 */


/**
 * @file
 * Core module functions for the Domain Access suite.
 * @ingroup domain
 */


/**
 * Defines how to handle access permissions when installing the module.
 * You may alter this variable before installing the module. See README.txt.
 */

define('DOMAIN_INSTALL_RULE', TRUE);

/**
 * Defines how to handle editor permissions when installing the module.
 * You may alter this variable before installing the module. See README.txt.
 */

define('DOMAIN_EDITOR_RULE', FALSE);

/**
 * Defines whether to show affiliated content on all domains..
 * You may alter this variable before installing the module. See README.txt.
 */

define('DOMAIN_SITE_GRANT', TRUE);

/**
 * Implements hook_init()
 *
 * Inititalizes a global $_domain variable and set it based on the
 * third-level domain used to access the page.
 */

function domain_init() {
  global $_domain, $conf;
  $_domain = array();
  // We lower case this, since EXAMPLE.com == example.com.
  $_subdomain = strtolower(rtrim($_SERVER['HTTP_HOST']));
  
    // Strip the www. off the subdomain, if required by the module settings.
  $raw_domain = $_subdomain;
  if (variable_get('domain_www', 0)) {
    $_subdomain = str_replace('www.', '', $_subdomain);
  }
  // Lookup the active domain against our allowed hosts record.
  $data = db_fetch_array(db_query("SELECT domain_id FROM {domain} WHERE subdomain = '%s'", $_subdomain));
  // Get the domain data.
  $_domain = domain_lookup($data['domain_id']);
  
    // If return is -1, then the DNS didn't match anything, so use defaults.
  if ($_domain == -1) {
    $_domain = domain_default();
  }
  // If we stripped the www. send the user to the proper domain.  This should only
  // happen once, on an inbound link or typed URL, so the overhead is acceptable.
  if ($raw_domain != $_subdomain) {
    drupal_goto(domain_get_uri($_domain));
  }
  
    // For Domain User, we check the validity of accounts, so the 'valid' flag must be TRUE.
  // If this check fails, we send users to the default site homepage.
  if (!$_domain['valid'] && !user_access('administer domains')) {
    $_domain = domain_default();
    drupal_goto($_domain['path']);
  }
  // Set the site name to the domain-specific name.
  $conf['site_name'] = $_domain['sitename'];
}

/**
 * Implements hook_menu()
 */

function domain_menu($may_cache) {
  $items = array();
  $admin = user_access('administer domains');
  if ($may_cache) {
    $items[] = array(
      'title' => t('Domains'),
      'path' => 'admin/build/domain',
      'access' => $admin,
      'callback' => 'domain_admin',
      'callback arguments' => array('configure'),
      'description' => t('Settings for the Domain Access module.')
    );
    $items[] = array(
      'title' => t('Settings'),
      'path' => 'admin/build/domain/settings',
      'access' => $admin,
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'callback' => 'domain_admin',
      'callback arguments' => array('configure'),
      'weight' => -10
    );
    $items[] = array(
      'title' => t('Domain list'),
      'path' => 'admin/build/domain/view',
      'access' => $admin,
      'type' => MENU_LOCAL_TASK,
      'callback' => 'domain_admin',
      'callback arguments' => array('view'),
      'weight' => -8
    );
    $items[] = array(
      'title' => t('Create domain record'),
      'path' => 'admin/build/domain/create',
      'access' => $admin,
      'type' => MENU_LOCAL_TASK,
      'callback' => 'domain_admin',
      'callback arguments' => array('create'),
      'weight' => -4
    );
    $items[] = array(
      'title' => t('Node settings'),
      'path' => 'admin/build/domain/advanced',
      'access' => $admin,
      'type' => MENU_LOCAL_TASK,
      'callback' => 'domain_admin',
      'callback arguments' => array('advanced'),
      'weight' => -2
    );
    // Register the batch actions as menu callbacks
    $batch = module_invoke_all('domainbatch');
    if (!empty($batch)) {
      $items[] = array(
        'title' => t('Batch updating'),
        'path' => 'admin/build/domain/batch',
        'access' => $admin,
        'type' => MENU_LOCAL_TASK,
        'callback' => 'domain_admin',
        'callback arguments' => array('batch'),
        'weight' => 0
      );
      // Get the submenu items
      foreach ($batch as $key => $value) {
        $items[] = array(
          'title' => $value['#form']['#title'],
          'path' => 'admin/build/domain/batch/'. $key,
          'access' => $admin,
          'type' => MENU_CALLBACK,
          'callback' => 'domain_admin',
          'callback arguments' => array('batch', $key),
          'weight' => $value['#weight']
        );
      }
    }
  }
  else {
    $items[] = array(
      'title' => t('Edit domain record'),
      'path' => 'admin/build/domain/edit',
      'access' => $admin,
      'type' => MENU_CALLBACK,
      'callback' => 'domain_admin',
      'callback arguments' => array('edit', arg(4))
    );
    $items[] = array(
      'title' => t('Delete domain record'),
      'path' => 'admin/build/domain/delete',
      'access' => $admin,
      'type' => MENU_CALLBACK,
      'callback' => 'domain_admin',
      'callback arguments' => array('delete', arg(4))
    );
    // Make sure that our default grant is set at all times.
    if (arg(0) == 'admin') {
      domain_set_default_grant();
    }
  }
  return $items;
}

/**
 * Implements hook_perm()
 */

function domain_perm() {
  $perms = array('administer domains', 'assign domain editors', 'edit domain nodes', 'set domain access', 'view domain publishing');
  return $perms;
}

/**
 * Implements hook_block()
 *
 * A nifty little domain-switcher block, useful during debugging.
 */

function domain_block($op = 'list', $delta = 0, $edit = array()) {
  global $_domain, $base_url;
  $blocks = array();
  switch ($op) {
    case 'list':
      $blocks[0] = array(
        'info' => t('Domain switcher'),
      );
      $blocks[1] = array(
       'info' => t('Domain access information'),
      );
      return $blocks;
      break;
    case 'view':
      switch ($delta) {
        case 0:
          $block['subject'] = t('Domain switcher');
          $items = array();
          $domains = domain_domains();
          $msg = FALSE;
          foreach ($domains as $domain) {
            if ($domain['valid']) {
              $title = $domain['sitename'];
              $allow = TRUE;
            }
            else {
              $title = $domain['sitename'] .' *';
              $allow = FALSE;
              if (user_access('administer domains')) {
                $msg = TRUE;
                $allow = TRUE;
              }
            }
            if ($allow) {
              $items[] = l($title, domain_get_uri($domain));
            }
          }
          $block['content'] = theme('item_list', $items);
          if ($msg) {
            $block['content'] .= t('<em>* Inactive domain.</em>');
          }
          break;
        case 1:
          $block['content'] = '';
          if (arg(0) == 'node' && is_numeric(arg(1))) {
            $block['subject'] = t('Domain access information');
            $this_node = node_load(arg(1));
            $output = '';
            if (!empty($this_node->subdomains)) {
              $output .= theme('item_list', $this_node->subdomains, t('Subdomains'));
            }
            if (!empty($this_node->editors)) {
              $output .= theme('item_list', $this_node->editors, t('Editors'));
            }
            if (isset($this_node->domain_source)) {
              $this_domain = domain_lookup($this_node->domain_source);
              $output .= theme('item_list', array($this_domain['sitename']), t('Source domain'));
            }
            if (empty($output)) {
              $output = t('This node is not assigned to a domain.');
            }
            $block['content'] = '<p>'. t('%node is published with the following Domain Access rules:', array('%node' => $this_node->title)) .'</p>'. $output;
          } 
            return $block;
          break;
      }
      return $block;
      break;
  }
}

/**
 * Implements hook_user()
 *
 * Attached domain_id records to all registering users.  These
 * are used to determine which 'domain_editor' group that users
 * with the 'edit domain nodes' permission are in.
 */

function domain_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'form':
    case 'register':
      if (is_null($category) || $category == 'account') {
        global $_domain;
        $result = db_query("SELECT domain_id, subdomain, sitename, scheme FROM {domain}");
        $options = array();
        // By default, the requesting domain is assigned.
        if (empty($account->domain_user)) {
          ($_domain['domain_id'] == 0) ? $default  = array(-1) : $default = array($_domain['domain_id']);
        }
        else {
          $default = $account->domain_user;
        }
        $options[-1] = variable_get('domain_sitename', variable_get('sitename', 'Drupal'));
        while ($data = db_fetch_array($result)) {
          $options[$data['domain_id']] = $data['sitename'];
        }
        if (user_access('assign domain editors')) {
          $form['domain_user'] = array(
            '#type' => 'fieldset',
            '#title' => t('Domain access'),
            '#collapsible' => TRUE,
            '#collapsed' => FALSE,
            '#weight' => 1
          );
          $form['domain_user']['domain_user'] = array(
            '#type' => 'checkboxes',
            '#options' => $options,
            '#title' => t('Domain access setttings'),
            '#description' => t('Select the affiliates that this user belongs to.  Used to grant editing permissions for users with the "edit domain nodes" permission.'),
            '#default_value' => $default
          );
        }
        else {
          $form['domain_user'] = array(
            '#type' => 'value',
            '#value' => $default
          );
        }
        return $form;
      }
      break;
    case 'validate':
      return array('domain_user' => $edit['domain_user']);
      break;
    case 'view':
      if (user_access('assign domain editors') && !empty($account->domain_user)) {
        $output = '<ul>';
        foreach ($account->domain_user as $id) {
          if (abs($id) > 0) {
            if ($id > 0) {
              $domain = domain_lookup($id);
              $output .= '<li>'. $domain['sitename'] .'</li>';
            }
            else {
              $output .= '<li>'. variable_get('domain_sitename', variable_get('sitename', 'Drupal')) .'</li>';
            }
          }
        }
        $output .= '</ul>';
        $items['domain'] = array('title' => t('Domain settings'),
          'value' => $output,
        );
        return array(t('Domain status') => $items);
      }
      break;
  }
}

/**
 * Implements hook_cron()
 *
 * This function invokes hook_domaincron() and allows
 * Domain Access modules to run functions for all active affiliates.
 */

function domain_cron() {
  global $_domain;
  // Check to see if this function is needed at all.
  $modules = module_implements('domaincron');
  if (!empty($modules)) {
    // Store the current $_domain global.
    $_temp = $_domain;
    // Get the domain list.
    $domains = domain_domains();
    // Run the hook for each active domain.
    foreach ($domains as $domain) {
      // Set the domain-specific variables
      if (function_exists('_domain_conf_load')) {
        _domain_conf_load($domain);       
              }
      // Set the global table prefix
      if (function_exists('_domain_prefix_load')) {
        _domain_prefix_load($domain);       
              }
      // Set the global to the current $domain.
      $_domain = $domain;
      foreach ($modules as $module) {
        module_invoke($module, 'domaincron', $domain);
      }
    }
    // Set the $_domain global back.
    $_domain = $_temp;
  }
}

/**
 * Router function to call various administration tasks
 *
 * @param $action
 *  The function to be performed.
 * @param $id
 *  The domain_id of the record to be acted upon.
 */

function domain_admin($action, $id = NULL) {
  include_once('domain_admin.inc');
  $func = 'domain_'. $action;
  return $func($id);
}

/**
 * Runs a lookup against the {domain} table.  One of the two values must be present
 *
 * This function also calls hook_domainload(), which lets module developers overwrite
 * or add to the $domain array.
 *
 * @param $domain_id
 *  The domain_id taken from {domain}. Optional.
 * @param $subdomain
 *  The string representation of a {domain} entry. Optional.
 * @param $reset
 *  A boolean flag to clear the static variable if necessary.
 * @return
 *  An array containing the requested row from the {domain} table, plus the
 *  elements added by hook_domainload().  Returns -1 on failure.
 */

function domain_lookup($domain_id = NULL, $subdomain = NULL, $reset = FALSE) {
  static $domains;
  // If both are NULL, no lookup can be run.
  if (is_null($domain_id) && is_null($subdomain)) {
    return -1;
  }
  // Create a unique key so we can static cache all requests.
  $key = $domain_id . $subdomain;
  // Run the lookup, if needed.
  if (!isset($domains[$key]) || $reset) {
    if ($subdomain) {
      $domain = db_fetch_array(db_query("SELECT domain_id, subdomain, sitename, scheme, valid FROM {domain} WHERE subdomain = '%s'", $subdomain));
    }
    else if ($domain_id == 0) {
      $domain = domain_default();
    }
    else {
      $domain = db_fetch_array(db_query("SELECT domain_id, subdomain, sitename, scheme, valid FROM {domain} WHERE domain_id = %d", $domain_id));
    }
    // Did we get a valid result?
    if (isset($domain['domain_id'])) {
      // Let Domain Access module extensions act to override the defaults.
      $domains[$key] = domain_api($domain);
    }
    else {
      $domains[$key] = -1;
    }
  }
  return $domains[$key];
}

/**
 * Assigns the default settings to domain 0, the root domain.
 *
 * This value is used throughout the modules, so needed abstraction.
 *
 * @param $reset
 * A boolean flag indicating whether to reset the static array or not.
 */

function domain_default($reset = FALSE) {
  static $default;
  if (empty($default) || $reset) {
    $default['domain_id'] = 0;
    $default['sitename'] = variable_get('domain_sitename', variable_get('sitename', 'Drupal'));
    $default['subdomain'] = variable_get('domain_root', '');
    $default['scheme'] = variable_get('domain_scheme', 'http');
    // Set the valid flag.
    $default['valid'] = TRUE;
    // Let submodules overwrite the defaults, if they wish.
    $default = domain_api($default);
  }
  return $default;
}

/**
 * Return all active domains (including the default) as an array.
 *
 * @param $reset
 * A boolean flag indicating whether to reset the static array or not.
 * @return
 * An array of all active domains, with the domain_id as the key.
 */

function domain_domains($reset = FALSE) {
  static $domains;
  if (empty($domains) || $reset) {
    $domains = array();
    $domains[] = domain_default($reset);
    // Query the db for active domain records.
    $result = db_query("SELECT domain_id FROM {domain}");
    while ($data = db_fetch_array($result)) {
      $domain = domain_lookup($data['domain_id'], NULL, TRUE);
      $domains[$domain['domain_id']] = $domain;
    }
  }
  $sort = variable_get('domain_sort', 'id');
  uasort($domains, '_domain_'. $sort  .'_sort');
  return $domains;
}

/**
 * Helper sort function
 */

function _domain_id_sort($a, $b) {
  return ($a['domain_id'] < $b['domain_id']) ? -1 : 1;
}

/**
 * Helper sort function
 */

function _domain_name_sort($a, $b) {
  return strcmp($a['sitename'], $b['sitename']);
}

/**
 * Helper sort function
 */

function _domain_url_sort($a, $b) {
  return strcmp($a['subdomain'], $b['subdomain']);
}

/**
 * Helper sort function
 */

function _domain_rid_sort($a, $b) {
  return ($a['domain_id'] > $b['domain_id']) ? -1 : 1;
}

/**
 * Helper sort function
 */

function _domain_rname_sort($a, $b) {
  return strcmp($b['sitename'], $a['sitename']);
}

/**
 * Helper sort function
 */

function _domain_rurl_sort($a, $b) {
  return strcmp($a['subdomain'], $b['subdomain']);
}

/**
 * Helper function for passing hook_domainload() by reference.
 *
 * @param $domain
 * The domain array defined by domain_lookup().
 * @return
 * The $domain array, modified by reference by hook_domainload() implementations.
 */

function domain_api($domain) {
  static $_modules;
  if (!isset($_modules)) {
    $_modules = module_implements('domainload');
  }
  if (!empty($_modules)) {
    foreach ($_modules as $module) {
      // Cannot use module_invoke_all() since these are passed by reference.
      $function = $module .'_domainload';
      $function($domain);
    }
  }
  return $domain;
}

/**
 * Implements hook_domainload()
 *
 * Adds the home page 'path' and 'site_grant' boolean.
 */

function domain_domainload(&$domain) {
  // Get the path to the home page for this domain.
  $domain['path'] = domain_get_path($domain);
  // Grant access to all affiliates.
  $domain['site_grant'] = DOMAIN_SITE_GRANT;
}

/**
 * Determine an absolute path for a domain
 *
 * @param $domain
 *  The currently active $domain array, provided by domain_lookup().
 */

function domain_get_path($domain) {
  global $base_url;
  $_url = parse_url($base_url);
  // PHP 5 does not return an empty path element.
  if (!isset($_url['path'])) {
    $_url['path'] = '/';
  }
  // We need a trailing slash at the end of the path
  if (substr($_url['path'], -1) != '/') {
    $_url['path'] .= '/';
  }
  $path = $domain['scheme'] .'://'. $domain['subdomain'] . $_url['path'];
  return $path;
}

/**
 * Determine an absolute path to the current page
 */

function domain_get_uri($domain) {
  $path = $domain['scheme'] .'://'. $domain['subdomain'] . request_uri();
  return $path;
}

/**
 * Determine if we must switch the active domain.
 *
 * This function will execute a drupal_goto() to pop users to the correct
 * domain.
 *
 * @param $domain
 *  The currently active $domain array, provided by domain_lookup().
 */

function domain_goto($domain) {
  global $_domain;
  // We must be on the proper domain, see http://drupal.org/node/186153.
  if ($domain != -1 && $_domain['domain_id'] != $domain['domain_id']) {
    $path = domain_get_uri($domain);
    drupal_goto($path);
  }
}

/**
 * Implements hook_nodeapi().
 *
 * This function is used to provide debugging information and to prep values from
 * the {domain_access} table when editing nodes.  Since not all users can see the
 * domain access editing checkboxes, we pass some node_access values as hidden elements.
 */

function domain_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'prepare':
    case 'load':
      // Append the domain grants to the node for editing.
      $node->domains = array();
      $node->editors = array();
      $node->domain_site = FALSE;
      $result = db_query("SELECT gid, realm FROM {domain_access} WHERE nid = %d AND (realm = '%s' OR realm = '%s' OR realm = '%s')", $node->nid, 'domain_id', 'domain_site', 'domain_editor');
      while ($data = db_fetch_object($result)) {
        // Transform the 0 to -1, since {domain_access} is unsigned.
        ($data->gid == 0) ? $gid = -1 : $gid = $data->gid;
        if ($data->realm == 'domain_id') {
          $node->domains[$gid] = $gid;
          if ($gid > 0) {
            $domain = domain_lookup($gid);
            $node->subdomains[] = $domain['sitename'];
          }
          else {
            $node->subdomains[] = variable_get('domain_sitename', variable_get('sitename', 'Drupal'));
          }
        }
        else if ($data->realm == 'domain_site') {
          $node->domain_site = TRUE;
          $node->subdomains[] = t('All affiliates');
        }
        else if ($data->realm == 'domain_editor') {
          $node->domain_editor = TRUE;
          if ($gid > 0) {
            $domain = domain_lookup($gid);
            $node->editors[] = $domain['sitename'];
          }
          else {
            $node->editors[] = variable_get('domain_sitename', variable_get('sitename', 'Drupal'));
          }
        }
      }
      break;
    case 'view':
      // Search module casts both $a3 and $a4 as FALSE, not NULL.
      // We check that to hide this data from search and other nodeapi
      // calls that are neither a teaser nor a page view.
      if ($a3 !== FALSE || $a4 !== FALSE) {
        $output = '';
        $debug = variable_get('domain_debug', 0);
        if ($debug && user_access('set domain access')) {
          if (!empty($node->subdomains)) {
            $output .= '<p><b>Subdomains</b></p><ul>';
            foreach ($node->subdomains as $name) {
              $output .= '<li>'. $name .'</li>';
            }
            $output .= '</ul>';
            $node->content['subdomains'] = array('#value' => $output, '#weight' => 20);
          }
          if (!empty($node->editors)) {
            $output = '<p><b>Editors</b></p><ul>';
            foreach ($node->editors as $name) {
              $output .= '<li>'. $name .'</li>';
            }
            $output .= '</ul>';
            $node->content['editors'] = array('#value' => $output, '#weight' => 21);
          }
          if (empty($output)) {
            $node->content['domain'] = array('#value' => t('This node is not assigned to a domain.'), '#weight' => 22);
          }
        }
      }
      break;
    case 'insert':
    case 'update':
      // Store these records in our own table as well.
      $grants = domain_node_access_records($node);
      _domain_write_records($node->nid, $grants);
      break;
    case 'delete':
      // Remove records from the {domain_access} table.
      db_query("DELETE FROM {domain_access} WHERE nid = %d", $node->nid);
      break;
  }
}

/**
 * Implements hook_node_grants.
 *
 * Informs the node access system what permissions the user has.  By design
 * all users are in the realm defined by the currently active domain_id.
 */

function domain_node_grants($account, $op) {
  global $_domain;

  // Do we need to use complex rules?
  $rules = variable_get('domain_access_rules', FALSE);

  // By design, all users can see content sent to all affiliates,
  // but the $_domain['site_grant'] can be set to FALSE.
  if ($op == 'view') {
    if ($_domain['site_grant']) {
      $grants['domain_site'][] = 0;
      if ($rules) {
        $grants['domain_site']['group'] = 'domain';
      }
    }

    // Grant based on active subdomain.
    $grants['domain_id'][] = $_domain['domain_id'];
    if ($rules) {
      $grants['domain_id']['group'] = 'domain';
    }
    // In special cases, we grant the ability to view all nodes.  That is,
    // we simply get out of the way of other node_access rules.
    // We do this with the universal 'domain_all' grant.
    if ($op == 'view' && domain_grant_all()) {
      // If no other node access modules are present, return our grant.
      // Otherwise, we just step out of the way.
      if ($rules) {
        return array();
      }
      else {
        return array('domain_all' => array(0));
      }
    }
  }
  else {
    // Special permissions for editors
    $editors = variable_get('domain_editors', DOMAIN_EDITOR_RULE);
    if ($editors && user_access('edit domain nodes', $account)) {
      if (!empty($account->domain_user)) {
        foreach ($account->domain_user as $id) {
          if (abs($id) > 0) {
            if ($id > 0) {
              $grants['domain_editor'][] = $id;
            }
            else {
              $grants['domain_editor'][] = 0;
            }
            // Advanced rules let us access check unpublished nodes for editing.
            if ($rules) {
              $grants['domain_editor']['check'] = TRUE;
            }
          }
        }
      }
    }
  }

  // Let Domain Access module extensions act to override the defaults.
  static $_modules;
  if (!isset($_modules)) {
    $_modules = module_implements('domaingrants');
  }
  if (!empty($_modules)) {
    foreach ($_modules as $module) {
      // Cannot use module_invoke_all() since these are passed by reference.
      $function = $module .'_domaingrants';
      $function($grants, $account, $op);
    }
  }
  return $grants;
}

/**
 * Implements hook_node_access_records()
 *
 * Set permissions for a node to be written to the database.  By design
 * if no options are selected, the node is assigned to the main site.
 */

function domain_node_access_records($node) {
  if (domain_disabling()) {
    return;
  }

  // How is core content handled for this site?  We need to run this
  // check when the module is first enabled.
  $behavior = variable_get('domain_behavior', DOMAIN_INSTALL_RULE);
  if (domain_enabling() && $behavior == 1) {
    $node->domain_site = TRUE;
  }

  // Define the $grants array.
  $grants = array();

  // If the form is hidden, we are passed the 'domains_raw' variable.
  // We need to append unique values from this variable to the existing
  // stored values.  See the logic for 'view domain publshing' in domain_form_alter().
  if (is_array($node->domains_raw)) {
    if (!isset($node->domains)) {
      $node->domains = array();
    }
    foreach ($node->domains_raw as $value) {
      // Only add this if it is not present already.
      if (!in_array($value, $node->domains)) {
        $node->domains[$value] = $value;
      }
    }
  }

  // If set, grant access to the core site, otherwise
  // The grant must be explicitly given to a domain.
  if ($node->domain_site) {
    $grants[] = array(
      'realm' => 'domain_site',
      'gid' => 0,
      'grant_view' => TRUE,
      'grant_update' => FALSE,
      'grant_delete' => FALSE,
      'priority' => 0,         // If this value is > 0, then other grants will not be recorded
    );
  }
  // Special permissions for editors, if activated.
  $editors = variable_get('domain_editors', DOMAIN_EDITOR_RULE);
  if (!empty($node->domains)) {
    foreach ($node->domains as $key => $value) {
      // We can't use a 0 value in an $options list, so convert -1 to 0.
      if (abs($value) > 0) {
        ($key == -1) ? $key = 0 : $key = $key;
        $grants[] = array(
          'realm' => 'domain_id',
          'gid' => $key,
          'grant_view' => TRUE,
          'grant_update' => FALSE,
          'grant_delete' => FALSE,
          'priority' => 0,
        );
        if ($editors) {
          $grants[] = array(
            'realm' => 'domain_editor',
            'gid' => $key,
            'grant_view' => FALSE,
            'grant_update' => TRUE,
            'grant_delete' => TRUE,
            'priority' => 0,
          );
        }
      }
    }
  }
  // At least one option must be present, and it is the default site
  // this prevents null values in the form.
  // If we are enabling the module for the first time, we set the
  // default domain of all existing nodes to the root domain.
  if (empty($grants) || domain_enabling()) {
    $grants[] = array(
    'realm' => 'domain_id',
    'gid' => 0,
    'grant_view' => TRUE,
    'grant_update' => FALSE,
    'grant_delete' => FALSE,
    'priority' => 0,
    );
    if ($editors) {
      $grants[] = array(
        'realm' => 'domain_editor',
        'gid' => 0,
        'grant_view' => FALSE,
        'grant_update' => TRUE,
        'grant_delete' => TRUE,
        'priority' => 0,
      );
    }
  }

  // Let Domain Access module extensions act to override the defaults.
  static $_modules;
  if (!isset($_modules)) {
    $_modules = module_implements('domainrecords');
  }
  if (!empty($_modules)) {
    foreach ($_modules as $module) {
      // Cannot use module_invoke_all() since these are passed by reference.
      $function = $module .'_domainrecords';
      $function($grants, $node);
    }
  }
  return $grants;
}

/**
 * Store node_access records in the {domain_access{} table.
 *
 * @param $nid
 * The node id being acted upon.
 * @param $grants
 * The grants passed by hook_node_access_records().
 */

function _domain_write_records($nid, $grants = array()) {
  if ($nid > 0 && !empty($grants)) {
    db_query("DELETE FROM {domain_access} WHERE nid = %d", $nid);
    foreach ($grants as $grant) {
      db_query("INSERT INTO {domain_access} (nid, gid, realm) VALUES (%d, %d, '%s')", $nid, $grant['gid'], $grant['realm']);
    }
  }
}

/**
 * Upon enabling this module, store the default view grant
 * in the {node_access} table.
 * @see domain_grant_all()
 */

function domain_enable() {
  domain_enabling(TRUE);
  // Rebuild the node access table with our rules.
  node_access_rebuild();
  // Set the default 'domain_all' grant for special pages.
  domain_set_default_grant();
  // Put all the grant records into the {domain_access} table.
  $realms = array('domain_site', 'domain_id', 'domain_editor');
  foreach ($realms as $realm) {
    $result = db_query("SELECT * FROM {node_access} WHERE realm = '%s'", $realm);
    while ($data = db_fetch_array($result)) {
      db_query("INSERT INTO {domain_access} VALUES (%d, %d, '%s')", $data['nid'], $data['gid'], $data['realm']);
    }
  }
}

/**
 * Ensure that the 'domain_all' grant is present.
 */

function domain_set_default_grant() {
  $check = db_result(db_query("SELECT COUNT(nid) FROM {node_access} WHERE realm = 'domain_all' AND gid = 0"));
  if (!$check) {
    db_query("INSERT INTO {node_access} VALUES (0, 0, 'domain_all', 1, 0, 0)");
  }
}

/**
 * Writes the default grants when the module is first enabled.
 */

function domain_enabling($set = NULL) {
 &