You are here

domain.module in Domain Access 7.3

Same filename and directory in other branches
  1. 5 domain.module
  2. 6.2 domain.module
  3. 7.2 domain.module

Core module functions for the Domain Access suite.

File

domain.module
View source
<?php

/**
 * @defgroup domain Domain Access: A domain-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 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);

/**
 * Defines whether to assign users to the default domain on install.
 * You may alter this variable before installing the module. See README.txt.
 */
define('DOMAIN_ASSIGN_USERS', TRUE);

/**
 * Sets a default value at which to start paginating Domain lists.
 */
define('DOMAIN_LIST_SIZE', 25);

/**
 * Defines the export statuses for a domain.
 */
define('DOMAIN_EXPORT_STATUS_DATABASE', 0);
define('DOMAIN_EXPORT_STATUS_CODE', 1);

/**
 * Defines constants for content settings.
 */
define('DOMAIN_ALL', 'DOMAIN_ALL');
define('DOMAIN_ACTIVE', 'DOMAIN_ACTIVE');

/**
 * Ensures that our custom_url_rewrite_outbound() is loaded.
 *
 * @link http://drupal.org/node/529026
 * @link http://drupal.org/node/820062
 */
function domain_boot() {
  include_once 'settings_custom_url.inc';
}

/**
 * Notify other modules of our API version.
 */
function domain_api_version() {
  return 3;
}

/**
 * Implements hook_init().
 *
 * Initializes a global $_domain variable if necessary (usually that's done in
 * domain_bootstrap.inc) and loads information on current domain.
 *
 * Also handles www stripping, checks the validity of user domains and updates
 * $conf['site_name'].
 */
function domain_init() {
  global $_domain, $conf;
  if (!is_array($_domain)) {
    $_domain = array();
  }

  // Error handling in case the module is not installed correctly.
  if (!isset($_domain['domain_id'])) {
    $_domain = domain_default(TRUE);
    $_domain['error'] = 'bootstrap include';
  }

  // If $_domain['error'] is present, then set a message and stop.
  if (!isset($error) && isset($_domain['error'])) {
    $error = 'Domain access failed to load during phase: ' . $_domain['error'] . '. Please check your settings.php file and site configuration.';

    // Do not show on form submissions, when enabling the module.
    if (empty($_POST)) {

      // Show a warning to admin users, if enabled.
      // You may disable this warning by adding:
      // $conf['domain_hide_errors'] = TRUE;
      // to the bottom of settings.php.
      $hide = variable_get('domain_hide_errors', FALSE);
      if (user_access('administer domains') && empty($hide)) {
        drupal_set_message($error, 'error');
      }
      if (empty($hide)) {
        watchdog('domain', $error, NULL, WATCHDOG_ERROR);
      }
    }
  }

  // End of the error handling routine.
  // If coming from a node save, make sure we are on an accessible domain.
  domain_node_save_redirect();

  // Strip the www. off the domain, if required by the module settings.
  $www_replaced = FALSE;
  if (variable_get('domain_www', 0) && strpos($_domain['subdomain'], 'www.') !== FALSE) {
    $_domain['subdomain'] = str_replace('www.', '', $_domain['subdomain']);
    $www_replaced = TRUE;
  }

  // Add information from domain_lookup but keep existing values (domain_id and subdomain)
  $domain = domain_lookup($_domain['domain_id'], NULL, TRUE);
  if ($domain != -1) {
    $_domain = array_merge($domain, $_domain);
  }

  // Set the initial domain record, for later reference. See http://drupal.org/node/706490.
  domain_initial_domain($_domain);

  // If we have replaced 'www.' in the url, redirect to the clean domain.
  if ($www_replaced) {
    drupal_goto(domain_get_uri($_domain));
  }

  // For Domain User, we check the validity of accounts, so the 'valid' flag must be TRUE.
  if (empty($_domain['valid'])) {
    domain_invalid_domain_requested();
  }

  // Set the site name to the domain-specific name.
  if (variable_get('domain_sitename_override', 1)) {
    $conf['site_name'] = $_domain['sitename'];
  }
}

/**
 * Store the initially loaded domain, for later use.
 *
 * @param $domain
 *   The domain global value. This should only be called once.
 *
 * @return $initial
 *   A copy of the initial $_domain global value.
 *
 * @see domain_init()
 */
function domain_initial_domain($domain = array()) {
  $initial =& drupal_static(__FUNCTION__);
  if (!isset($initial) && !empty($domain)) {
    $initial = $domain;
  }
  return $initial;
}

/**
 * Unserialize an object stored in {domain_*} tables.
 *
 * PostgreSQL has issues with bytea fields, and while this is
 * handled cleanly in cache_get(), we have our own functions
 * for retrieving similar data objects. So we must be sure to
 * unserialize these safely.
 *
 * This may have been fixed in Drupal 7.
 *
 * @param $object
 *   The serialized object.
 *
 * @return $data
 *   Properly unserialized data or an empty string if the $object
 *   contained no data.
 *
 * @see http://drupal.org/node/686146
 */
function domain_unserialize($object) {
  if (empty($object)) {
    return;
  }
  return unserialize($object);
}

/**
 * Implements hook_menu().
 */
function domain_menu() {
  $items = array();
  $admin = user_access('administer domains');
  $items['admin/structure/domain'] = array(
    'title' => 'Domains',
    'access arguments' => array(
      'administer domains',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_overview_form',
    ),
    'file' => 'domain.admin.inc',
    'description' => 'Manage and configure domains.',
  );
  $items['admin/structure/domain/view'] = array(
    'title' => 'Domain list',
    'access arguments' => array(
      'administer domains',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_overview_form',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'domain.admin.inc',
    'description' => 'View domains for the site.',
    'weight' => -50,
  );
  $items['admin/structure/domain/settings'] = array(
    'title' => 'Settings',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'domain_configure',
    'file' => 'domain.admin.inc',
    'description' => 'Configure Domain Access settings.',
    'weight' => -20,
  );
  $items['admin/structure/domain/create'] = array(
    'title' => 'Create domain',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_LOCAL_ACTION,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_form',
    ),
    'file' => 'domain.admin.inc',
    'description' => 'Create new domain record.',
  );

  // Register the batch actions as menu callbacks
  $batch = module_invoke_all('domain_batch');
  if (!empty($batch)) {
    $items['admin/structure/domain/batch'] = array(
      'title' => 'Batch updating',
      'access arguments' => array(
        'administer domains',
      ),
      'type' => MENU_LOCAL_TASK,
      'page callback' => 'domain_batch',
      'file' => 'domain.admin.inc',
      'description' => 'Batch update domain settings.',
      'weight' => -15,
    );

    // Get the submenu items
    foreach ($batch as $key => $value) {
      $items['admin/structure/domain/batch/' . $key] = array(
        'title' => $value['#form']['#title'],
        'access arguments' => isset($value['#permission']) ? array(
          $value['#permission'],
        ) : array(
          'administer domains',
        ),
        'type' => MENU_VISIBLE_IN_BREADCRUMB,
        'page callback' => 'domain_batch',
        'page arguments' => array(
          $key,
        ),
        'file' => 'domain.admin.inc',
        'description' => isset($value['#description']) ? $value['#description'] : '',
        'weight' => isset($value['#weight']) ? $value['#weight'] : 0,
      );
    }
  }
  $items['admin/structure/domain/nodes'] = array(
    'title' => 'Content defaults',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_nodes_form',
    ),
    'file' => 'domain.admin.inc',
    'description' => 'Default domain settings for content.',
    'weight' => -10,
  );
  $items['admin/structure/domain/roles'] = array(
    'title' => 'User defaults',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_roles_form',
    ),
    'file' => 'domain.admin.inc',
    'description' => 'Default domain settings for users.',
    'weight' => -5,
  );
  $items['admin/structure/domain/view/%domain'] = array(
    'title' => 'View',
    'title callback' => 'domain_title',
    'title arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer domains',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_form',
      4,
    ),
    'description' => 'Edit domain record.',
    'file' => 'domain.admin.inc',
    'weight' => -10,
  );
  $items['admin/structure/domain/view/%domain/edit'] = array(
    'title' => 'Edit',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/structure/domain/view/%domain/delete'] = array(
    'title' => 'Delete',
    'access arguments' => array(
      'administer domains',
    ),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_delete_form',
      4,
    ),
    'description' => 'Delete domain record.',
    'file' => 'domain.admin.inc',
    'weight' => 50,
  );
  $items['admin/structure/domain/repair'] = array(
    'title' => 'Domain update database',
    'access arguments' => array(
      'administer domains',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'domain_repair_form',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'domain.admin.inc',
  );
  return $items;
}

/**
 * Set the title of a menu callback for domain edits.
 *
 * @param $domain
 *   The domain array.
 *
 * @return
 *   A title string.
 */
function domain_title($domain) {
  return $domain['subdomain'];
}

/**
 * Implements hook_permission().
 */
function domain_permission() {
  $permissions = array(
    'administer domains' => array(
      'title' => t('Administer domain records and settings'),
      'restrict access' => TRUE,
    ),
    'access inactive domains' => array(
      'title' => t('Access inactive domains'),
      'restrict access' => TRUE,
    ),
    'assign domain editors' => array(
      'title' => t('Assign editors to domains'),
    ),
    'set domain access' => array(
      'title' => t('Set domain access status for all content'),
    ),
    'publish to any assigned domain' => array(
      'title' => t('Publish content to any assigned domain'),
    ),
    'publish from assigned domain' => array(
      'title' => t('Publish content only from assigned domain'),
    ),
    'publish from default domain' => array(
      'title' => t('Publish content only from the default domain'),
    ),
    'edit domain content' => array(
      'title' => t('Edit any content on assigned domains'),
    ),
    'delete domain content' => array(
      'title' => t('Delete any content on assigned domains'),
    ),
    'view unpublished domain content' => array(
      'title' => t('View unpublished content on assigned domains'),
    ),
  );

  // Generate standard node permissions for all applicable node types.
  foreach (node_permissions_get_configured_types() as $type) {
    $permissions += domain_editor_list_permissions($type);
  }
  return $permissions;
}

/**
 * Helper function to generate standard node permission list for a given type.
 *
 * Shamelessly lifted from node_list_permissions().
 *
 * @param $type
 *   The machine-readable name of the node type.
 * @return array
 *   An array of permission names and descriptions.
 */
function domain_editor_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 on assigned domains" => array(
      'title' => t('%type_name: Create new content on assigned domains', array(
        '%type_name' => $info->name,
      )),
    ),
    "update {$type} content on assigned domains" => array(
      'title' => t('%type_name: Edit any content on assigned domains', array(
        '%type_name' => $info->name,
      )),
    ),
    "delete {$type} content on assigned domains" => array(
      'title' => t('%type_name: Delete any content on assigned domains', array(
        '%type_name' => $info->name,
      )),
    ),
  );
  return $perms;
}

/**
 * Implements hook_theme().
 */
function domain_theme($existing, $type, $theme, $path) {
  $themes = array(
    'domain_batch_form' => array(
      'render element' => 'form',
      'file' => 'domain.admin.inc',
    ),
    'domain_batch_title' => array(
      'variables' => array(
        'batch' => array(),
      ),
      'file' => 'domain.admin.inc',
    ),
    'domain_nodes_form' => array(
      'render element' => 'form',
      'file' => 'domain.admin.inc',
    ),
    'domain_roles_form' => array(
      'render element' => 'form',
      'file' => 'domain.admin.inc',
    ),
    'domain_overview_form' => array(
      'render element' => 'form',
      'file' => 'domain.admin.inc',
    ),
  );
  return $themes;
}

/**
 * Implements hook_hook_info().
 *
 * Allows the use of $module.domain.inc files.
 */
function domain_hook_info() {
  $hooks['domain_load'] = array(
    'group' => 'domain',
  );
  $hooks['domain_insert'] = array(
    'group' => 'domain',
  );
  $hooks['domain_update'] = array(
    'group' => 'domain',
  );
  $hooks['domain_delete'] = array(
    'group' => 'domain',
  );
  $hooks['domain_reassign'] = array(
    'group' => 'domain',
  );
  $hooks['domain_cron'] = array(
    'group' => 'domain',
  );
  $hooks['domain_features_rebuild'] = array(
    'group' => 'domain',
  );
  $hooks['domain_install'] = array(
    'group' => 'domain',
  );
  $hooks['domain_ignore'] = array(
    'group' => 'domain',
  );

  // Replace with form alter?
  $hooks['domain_form'] = array(
    'group' => 'domain',
  );
  $hooks['domain_warning'] = array(
    'group' => 'domain',
  );
  $hooks['domain_source_alter'] = array(
    'group' => 'domain',
  );
  $hooks['domain_source_path_alter'] = array(
    'group' => 'domain',
  );
  $hooks['domain_batch'] = array(
    'group' => 'domain',
  );
  $hooks['domain_batch_alter'] = array(
    'group' => 'domain',
  );

  // Test that these work.
  $hooks['domain_bootstrap_lookup'] = array(
    'group' => 'domain',
  );

  // Test that these work.
  $hooks['domain_bootstrap_full'] = array(
    'group' => 'domain',
  );

  // Rename to hook_domain_path
  $hooks['domain_path'] = array(
    'group' => 'domain',
  );

  // Rename to hook_domain_warning_alter
  $hooks['domain_warning_alter'] = array(
    'group' => 'domain',
  );

  // Replace with form_alter?
  $hooks['domain_settings'] = array(
    'group' => 'domain',
  );
  $hooks['domain_validate_alter'] = array(
    'group' => 'domain',
  );
  return $hooks;
}

/**
 * Implements hook_block_info().
 */
function domain_block_info() {
  $blocks = array();
  $blocks['information'] = array(
    'info' => t('Domain access information'),
  );
  $blocks['server'] = array(
    'info' => t('Domain access server information'),
    'cache' => DRUPAL_NO_CACHE,
  );
  $blocks['switcher'] = array(
    'info' => t('Domain switcher'),
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function domain_block_view($delta = '') {

  // Dispatch to sub-function.
  if (empty($delta)) {
    return;
  }
  module_load_include('inc', 'domain', 'domain.blocks');
  $function = 'domain_block_view_' . $delta;
  if (function_exists($function)) {
    return $function();
  }
}

/**
 * Implements hook_user_load().
 *
 * Attached domain_id records to all registering users.  These
 * are used to determine which 'domain_editor' group that users
 * with the 'edit domain content' and 'delete domain content' permissions are in.
 */
function domain_user_load($users) {
  foreach ($users as $uid => $account) {
    $users[$uid]->domain_user = domain_get_user_domains($account);
  }
}

/**
 * Emsures that a user object has been loaded properly.
 *
 * @param $account
 *   An account object.
 */
function domain_user_set($account) {
  if (!isset($account->domain_user)) {
    $accounts = array(
      $account->uid => $account,
    );
    domain_user_load($accounts);
  }
}

/**
 * Implements hook_user_insert().
 */
function domain_user_insert(&$edit, $account, $category) {
  domain_user_save($edit, $account, $category);
}

/**
 * Implements hook_user_update().
 */
function domain_user_update(&$edit, $account, $category) {
  domain_user_save($edit, $account, $category);
}

/**
 * Helper function called by both hook_user_insert() and hook_user_update().
 */
function domain_user_save(&$edit, $account, $category) {

  // If our field element is missing, do nothing.
  if (!isset($edit['domain_user'])) {
    return;
  }

  // Clear and reset the {domain_editor} table.
  db_delete('domain_editor')
    ->condition('uid', $account->uid)
    ->execute();

  // Store the new domains.
  $values = array();
  foreach ($edit['domain_user'] as $domain_id => $status) {
    if ($status != 0) {
      $values[] = array(
        'uid' => $account->uid,
        'domain_id' => $domain_id,
      );
    }
  }
  if (!empty($values)) {
    $query = db_insert('domain_editor')
      ->fields(array(
      'uid',
      'domain_id',
    ));
    foreach ($values as $record) {
      $query
        ->values($record);
    }
    $query
      ->execute();
  }

  // Clear the $edit field.
  $edit['domain_user'] = NULL;
}

/**
 * Implements hook_user_delete().
 */
function domain_user_delete($account) {
  db_delete('domain_editor')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * Implements hook_user_view().
 */
function domain_user_view($account, $view_mode) {
  domain_user_set($account);

  // Only show on full view.
  if ($view_mode != 'full') {
    return;
  }

  // Only show trusted users.
  // TODO: Make this a new permission.
  if (!user_access('assign domain editors')) {
    return;
  }
  $output = '';
  $account->content['domain'] = array(
    '#type' => 'user_profile_category',
    '#weight' => 10,
    '#title' => t('Domain status'),
  );
  if (empty($account->domain_user)) {
    $output = t('This user is not assigned to a domain.');
  }
  else {
    $items = array();
    foreach (array_filter($account->domain_user) as $id) {
      $domain = domain_lookup($id);
      $items[] = check_plain($domain['sitename']);
    }
    $output = theme('item_list', array(
      'items' => $items,
    ));
  }
  $account->content['domain']['domain_settings'] = array(
    '#type' => 'user_profile_item',
    '#title' => t('Assigned domains'),
    '#markup' => $output,
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function domain_form_user_profile_form_alter(&$form, &$form_state) {
  domain_form_user_form_alter($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function domain_form_user_register_form_alter(&$form, &$form_state) {
  domain_form_user_form_alter($form, $form_state);
}

/**
 * Helper function for the two user forms we care about.
 */
function domain_form_user_form_alter(&$form, &$form_state) {

  // Only act on our forms.
  if (is_null($form['#user_category']) || !in_array($form['#user_category'], array(
    'account',
    'register',
  ))) {
    return;
  }
  $_domain = domain_get_domain();

  // Get the domains for this user, but ignore roles unless told to use them.
  $add_roles = variable_get('domain_add_roles', 0);

  // In the register case, we take the 'new user' settings.
  if ($form['#user_category'] == 'register') {
    $add_roles = TRUE;
  }
  $account = $form['#user'];
  $account->domain_user = domain_get_user_domains($account, $add_roles);

  // By default, the requesting domain is assigned on registration.
  if (empty($account->uid)) {
    $default = array(
      $_domain['domain_id'] => $_domain['domain_id'],
    );
  }
  else {
    $default = $account->domain_user;
  }
  if (user_access('assign domain editors')) {

    // Set the form options.
    $domains = domain_domains();
    $options = array();
    foreach ($domains as $domain) {
      $options[$domain['domain_id']] = check_plain($domain['sitename']);
    }
    $format = domain_select_format();
    $form['domain'] = array(
      '#type' => 'fieldset',
      '#title' => t('Domain access'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['domain']['domain_user'] = array(
      '#type' => empty($format) ? 'checkboxes' : 'select',
      '#options' => $options,
      '#title' => t('Domain access settings'),
      '#description' => t('Select the affiliates that this user belongs to.  Used to grant editing permissions for users with the "edit domain content" permission.'),
      '#default_value' => $default,
    );
    if ($format) {
      $form['domain']['domain_user']['#multiple'] = TRUE;
      $form['domain']['domain_user']['#size'] = count($options) > 10 ? 10 : count($options);
    }
  }
  else {
    $form['domain'] = array(
      'domain_user' => array(
        '#type' => 'value',
        '#value' => $default,
      ),
    );
  }
}

/**
 * Implements hook_user_operations().
 */
function domain_user_operations() {
  if (!user_access('assign domain editors')) {
    return;
  }
  return array(
    'domain' => array(
      'label' => t('Assign users to domains'),
      'callback' => 'domain_user_operation_assign',
    ),
  );
}

/**
 * Implements hook_form_alter().
 */
function domain_form_user_admin_account_alter(&$form, $form_state) {
  global $_domain;
  if (!user_access('assign domain editors')) {
    return;
  }
  $form['options']['#weight'] = -2;
  $_domain = domain_get_domain();
  $options = array();
  $format = domain_select_format();
  foreach (domain_domains() as $data) {

    // The domain must be valid.
    if ($data['valid'] || user_access('access inactive domains')) {

      // Filter checkbox output but not select list.
      $options[$data['domain_id']] = empty($format) ? check_plain($data['sitename']) : $data['sitename'];
    }
  }
  $form['domain'] = array(
    '#type' => 'fieldset',
    '#title' => t('Affiliate editor options'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#prefix' => '<div class="description">' . t('If you select <em>Assign users to domains</em> above, you should confirm the <em>Affiliate editor options</em> settings below.') . '</div>',
    '#weight' => -1,
  );
  $form['domain']['behavior'] = array(
    '#type' => 'radios',
    '#title' => t('Update behavior'),
    '#options' => array(
      0 => t('Replace old values with new settings'),
      1 => t('Add new settings to existing values'),
      2 => t('Remove selected domains from existing values'),
    ),
    '#description' => t('Defines how new grants will be applied to the updated users.'),
    '#default_value' => 0,
  );
  $form['domain']['domains'] = array(
    '#type' => empty($format) ? 'checkboxes' : 'select',
    '#title' => t('Assign to'),
    '#options' => $options,
    '#required' => FALSE,
    '#description' => t('Select which affiliates these users should belong to. <em>Note: this will erase any current assignment for the selected users.</em>'),
    '#default_value' => array(
      $_domain['domain_id'],
    ),
  );
  if ($format) {
    $form['domain']['domains']['#multiple'] = TRUE;
    $form['domain']['domains']['#size'] = count($options) > 10 ? 10 : count($options);
  }

  // Add our domain elements.
  $ops = array_pop($form['accounts']['#header']);
  $form['accounts']['#header']['domain_user'] = array(
    'data' => t('Assigned Domains'),
  );
  foreach (array_keys($form['accounts']['#options']) as $uid) {
    $form['accounts']['#options'][$uid]['domain_user'] = theme('item_list', array(
      'items' => _domain_user_list($uid),
    ));
  }
  $form['accounts']['#header']['operations'] = $ops;
  $form['#submit'][] = 'domain_update_users';
}

/**
 * Helper function to get the names of all domains for a user.
 *
 * @param $uid
 *   The user id.
 *
 * @return
 *   An array of domain names.
 */
function _domain_user_list($uid) {
  $temp_account = new stdClass();
  $temp_account->uid = $uid;
  $list = domain_get_user_domains($temp_account, FALSE);
  $domains = domain_domains();
  $user_domains = array();
  foreach ($list as $domain_id) {
    $user_domains[] = check_plain($domains[$domain_id]['sitename']);
  }
  return $user_domains;
}

/**
 * Callback for domain_content_node_operations().
 *
 * This callback is required, but we actually do our action inside
 * of domain_update_users().
 */
function domain_user_operation_assign($accounts) {
}

/**
 * FormsAPI to handle the batch update of users.
 */
function domain_update_users($form, &$form_state) {
  $values = $form_state['values'];
  if ($values['operation'] != 'domain') {
    return;
  }

  // Get the domains for this user, but ignore roles unless told to use them.
  $add_roles = variable_get('domain_add_roles', 0);

  // Loop through the selected accounts.
  $domains = array_filter($values['domains']);
  foreach ($values['accounts'] as $uid) {

    // If appending values, do so here.
    if (!empty($form_state['values']['behavior'])) {
      $account = new stdClass();
      $account->uid = $uid;
      $current = domain_get_user_domains($account, $add_roles);

      // Behavior 1: add new domains.
      if ($form_state['values']['behavior'] == 1) {
        $domains += $current;
      }
      else {
        foreach ($domains as $domain_id) {
          if (isset($current[$domain_id])) {
            unset($current[$domain_id]);
          }
        }
        $domains = $current;
      }
    }
    db_delete('domain_editor')
      ->condition('uid', $uid)
      ->execute();
    foreach ($domains as $domain_id) {
      db_insert('domain_editor')
        ->fields(array(
        'uid' => $uid,
        'domain_id' => $domain_id,
      ))
        ->execute();
    }
  }
}

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

  // Check to see if this function is needed at all.
  $modules = module_implements('domain_cron');
  if (!empty($modules)) {

    // Get the domain list.
    $domains = domain_domains();

    // Run the hook for each active domain.
    foreach ($domains as $domain) {
      domain_set_domain($domain['domain_id'], TRUE);
      foreach ($modules as $module) {
        module_invoke($module, 'domain_cron', $domain);
      }
    }

    // Reset the active domain.
    domain_reset_domain(TRUE);
  }
}

/**
 * Menu loader function.
 *
 * Note that this function should only be used as a menu callback,
 * since the results are not cached. Use domain_lookup() instead.
 *
 * The passed parameter will be checked against the {domain} table for
 * valid records.
 *
 * @param $domain_id
 *   The id request for a specific domain.
 * @param $reset
 *   A boolean flag to clear the static variable if necessary.
 *
 * @return
 *   $domain array on success or FALSE on failure.
 */
function domain_load($domain_id = NULL, $reset = FALSE) {
  $domain = domain_lookup($domain_id, NULL, $reset);
  if ($domain == -1) {
    return FALSE;
  }
  return $domain;
}

/**
 * Save a domain record.
 *
 * @param $values
 *   Form value information required to edit a domain.
 * @param $form_values
 *   Form values passed to the submit handler. May be used by other
 *   modules.
 *
 * @return
 *   $domain array on success or -1 on failure.
 */
function domain_save($values, $form_values = array()) {

  // Must this be the default domain?
  // Used in cases where there are no domains present.
  $count = (bool) db_query("SELECT COUNT(domain_id) FROM {domain} WHERE is_default = 1")
    ->fetchField();
  if (!$count) {
    $values['is_default'] = 1;
  }

  // Ensure we have a machine name.
  if (!isset($values['machine_name'])) {
    $values['machine_name'] = domain_machine_name($values['subdomain']);
  }

  // Update or insert a record?
  if (!empty($values['domain_id'])) {
    $action = 'domain_update';
    $update_id = array(
      'domain_id',
    );
  }
  else {
    $action = 'domain_insert';
    $update_id = array();
  }

  // If this is the default domain, reset other domains.
  if (!empty($values['is_default'])) {
    db_update('domain')
      ->fields(array(
      'is_default' => 0,
    ))
      ->condition('machine_name', $values['machine_name'], '<>')
      ->execute();
  }

  // Store the data, using the machine_name to generate a numeric id.
  // Note that we _must_ have a numeric key for {node_access}.
  drupal_write_record('domain_export', $values, $update_id);
  drupal_write_record('domain', $values, $update_id);

  // Let other modules act on a proper copy of the domain.
  $domain = domain_lookup(NULL, $values['subdomain'], TRUE);
  module_invoke_all($action, $domain, $form_values);

  // Lookup the modified record and return it.
  $domain = domain_lookup(NULL, $values['subdomain'], TRUE);

  // Clear static caches.
  domain_static_reset();

  // In all cases, we need to force a menu rebuild, which also clears the cache.
  variable_set('menu_rebuild_needed', TRUE);
  return $domain;
}

/**
 * Clear static caches used for domain listings.
 */
function domain_static_reset() {
  drupal_static_reset('domain_lookup_domains');
  drupal_static_reset('domain_lookup_result');
  drupal_static_reset('domain_id_list');
  drupal_static_reset('domain_domains');
  drupal_static_reset('domain_list_by_machine_name');
}

/**
 * Delete a domain record.
 *
 * @param $domain
 *   The domain record being deleted.
 * @param $values
 *   An array of values passed by a form submit, if any. When reassigning
 *   content from a domain, look for the 'domain_access' and 'domain_editor'
 *   keys, which are the domains ids for re-assignment.
 */
function domain_delete($domain, $values = array()) {

  // Let other modules act to reassign data.
  foreach (array(
    'domain_access',
    'domain_editor',
  ) as $table) {
    if (isset($values[$table]) && $values[$table] != 'none') {
      $new_domain = domain_lookup($values[$table]);
      if ($new_domain != -1) {
        domain_reassign($domain, $new_domain, $table);
      }
    }
  }

  // Inform other modules of the deletion.
  module_invoke_all('domain_delete', $domain, $values);

  // Remove domain-specific entries from tables and clear the cache.
  db_delete('domain_access')
    ->condition('gid', $domain['domain_id'])
    ->condition('realm', 'domain_id')
    ->execute();
  db_delete('domain_editor')
    ->condition('domain_id', $domain['domain_id'])
    ->execute();

  // Delete the domain.
  db_delete('domain')
    ->condition('domain_id', $domain['domain_id'])
    ->execute();
  db_delete('domain_export')
    ->condition('domain_id', $domain['domain_id'])
    ->execute();

  // In all cases, we need to force a menu rebuild, which also clears the cache.
  variable_set('menu_rebuild_needed', TRUE);

  // Notify that node access needs to be rebuilt.
  node_access_needs_rebuild(TRUE);
}

/**
 * Runs a lookup against the {domain} table.
 *
 * This function also calls hook_domain_load(), which lets module developers
 * overwrite or add to the $domain array.
 *
 * Note that this function should not be called prior to hook_init(). Lookups
 * run too early in the bootstrap process can cause registry issues.
 *
 * @param $domain_id
 *   The domain_id taken from {domain}. Optional, but one of the two values must
 *   be present
 * @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_domain_load().  Returns -1 on failure.
 */
function domain_lookup($domain_id = NULL, $subdomain = NULL, $reset = FALSE) {
  $domains =& drupal_static(__FUNCTION__ . '_domains');
  $result =& drupal_static(__FUNCTION__ . '_result');

  // Create a unique key so we can static cache all requests.
  $key = $domain_id . $subdomain;

  // Shortcut if we know the data.
  if (!$reset && isset($domains[$key])) {
    return $domains[$key];
  }

  // Get the data from the database. Doing this prevents extra queries.
  if (!isset($result) || $reset) {

    // Temporary hack for update to 7.x.3.
    if (db_table_exists('domain_export')) {
      $result = db_query("SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default, machine_name FROM {domain}", array(), array(
        'fetch' => PDO::FETCH_ASSOC,
      ))
        ->fetchAllAssoc('domain_id');
    }
    else {
      $result = db_query("SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default FROM {domain}", array(), array(
        'fetch' => PDO::FETCH_ASSOC,
      ))
        ->fetchAllAssoc('domain_id');
    }
  }

  // If both are NULL, no lookup can be run.
  if (is_null($domain_id) && is_null($subdomain) || $domain_id < 0) {
    $domains[$key] = -1;
  }
  elseif (!isset($domains[$key]) || $reset) {
    if ($subdomain) {
      foreach ($result as $array) {
        if ($array['subdomain'] == $subdomain) {
          $domain = $array;
        }
      }
    }
    elseif (isset($result[$domain_id])) {
      $domain = $result[$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, $reset);
    }
    else {
      $domains[$key] = -1;
    }
  }
  return $domains[$key];
}

/**
 * Assigns the default settings to domain 0, the root domain.
 *
 * This value is used throughout the modules. Even though this
 * record is in the {domain} table, we use the value stored as
 * a variable. Doing so prevents the module from firing when
 * it has not been configured.
 *
 * @param $reset
 *   A boolean flag indicating whether to reset the static array or not.
 * @param $alter
 *   A boolean flag indicating whether to allow hook_domain_load(). In
 *   some cases where external scripts do not pass an HTTP_HOST,
 *   Drupal does not behave as expected and we cannot trigger this
 *   API call.
 *
 * @see domain_request_name()
 *
 * @return
 *   The domain array for the default domain.
 */
function domain_default($reset = FALSE, $alter = TRUE) {
  $default =& drupal_static(__FUNCTION__);
  $altered =& drupal_static(__FUNCTION__ . '_altered');
  if (empty($default) || $reset) {
    $altered = FALSE;

    // Temporary hack for update to 7.x.3.
    if (db_table_exists('domain_export')) {
      $default = db_query("SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default, machine_name FROM {domain} WHERE is_default = 1")
        ->fetchAssoc();
    }
    else {
      $default = db_query("SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default FROM {domain} WHERE is_default = 1")
        ->fetchAssoc();
    }

    // Let submodules overwrite the defaults, if they wish.
    if (empty($default)) {
      $default = array(
        'domain_id' => 0,
        // Indicates we have not set a real domain.
        'subdomain' => isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '',
        'sitename' => variable_get('site_name', 'Drupal'),
        'scheme' => empty($_SERVER['HTTPS']) ? 'http' : 'https',
        'valid' => 1,
        'is_default' => 1,
        'machine_name' => domain_machine_name($default['subdomain']),
      );
    }
  }

  // If an altered version is requested, has $default been altered before?
  if ($alter && !$altered) {

    // Let submodules overwrite the defaults, if they wish.
    $default = domain_api($default, $reset);
  }
  return $default;
}

/**
 * Return the id of the default domain.
 *
 * Utility function for checking the default domain id; returns
 * only the 'domain_id' element of the default domain array.
 *
 * @return
 *   The domain_id of the default domain.
 */
function domain_default_id() {
  $id =& drupal_static(__FUNCTION__);
  if (!isset($id)) {

    // Do not allow domain_api() to run here, else we break the registry.
    // See http://drupal.org/node/1140898.
    $default = domain_default(FALSE, FALSE);
    $id = $default['domain_id'];
  }
  return $id;
}

/**
 * Return the machine_name of the default domain.
 *
 * @return
 *   The machine_name of the default domain.
 */
function domain_default_machine_name() {
  $id =& drupal_static(__FUNCTION__);
  if (!isset($id)) {

    // Do not allow domain_api() to run here, else we break the registry.
    // See http://drupal.org/node/1140898.
    $default = domain_default(FALSE, FALSE);
    $machine_name = $default['machine_name'];
  }
  return $machine_name;
}

/**
 * Set the primary domain properly, if necessary.
 */
function domain_set_primary_domain() {
  $root = strtolower(rtrim($_SERVER['HTTP_HOST']));
  if ($error = domain_valid_domain($root)) {
    return;
  }
  $site = variable_get('site_name', 'Drupal');
  $scheme = 'http';
  if (!empty($_SERVER['HTTPS'])) {
    $scheme = 'https';
  }
  $check = (bool) db_query("SELECT COUNT(domain_id) FROM {domain} WHERE is_default = 1")
    ->fetchField();
  if (empty($check)) {
    $domain = array(
      'subdomain' => $root,
      'sitename' => $site,
      'scheme' => $scheme,
      'weight' => -1,
      'valid' => 1,
      'is_default' => 1,
      'machine_name' => domain_machine_name($root),
    );
    drupal_write_record('domain_export', $domain);
    drupal_write_record('domain', $domain);
    module_invoke_all('domain_insert', $domain, array());
  }
}

/**
 * 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) {
  $domains =& drupal_static(__FUNCTION__);
  if (empty($domains) || $reset) {
    $domains = array();

    // Query the db for active domain records.
    $result = domain_id_list($reset);
    foreach ($result as $domain_id) {
      $domain = domain_lookup($domain_id, NULL, $reset);
      $domains[$domain['domain_id']] = $domain;
    }
  }
  return $domains;
}

/**
 * Return all active domain_ids.
 *
 * @param $reset
 *   A boolean flag indicating whether to reset the static array or not.
 *
 * @return
 *   An array of domain_ids, keyed by machine_name.
 */
function domain_id_list($reset = FALSE) {
  $list =& drupal_static(__FUNCTION__);
  if (isset($list) && empty($reset)) {
    return $list;
  }
  $list = array();
  $result = db_query("SELECT domain_id, machine_name FROM {domain} ORDER BY weight");
  foreach ($result as $data) {
    $list[$data->machine_name] = $data->domain_id;
  }
  return $list;
}

/**
 * Determine the default format for domain list forms.
 */
function domain_select_format() {
  $domains = domain_domains();
  $format = 0;
  if (count($domains) > variable_get('domain_list_size', DOMAIN_LIST_SIZE)) {
    $format = 1;
  }
  return variable_get('domain_select_format', $format);
}

/**
 * Validates a domain string.
 *
 * @param string $subdomain
 *   The string to check for domain validity
 *
 * @return array
 *   List of error messages or empty array.
 */
function domain_validate($subdomain) {
  $error_list = array();

  // Validate the domains format generically for now.
  $error = domain_valid_domain($subdomain);
  if (!empty($error)) {
    $error_list[] = $error;
  }

  // Make sure domain is unique
  if (!domain_unique_domain($subdomain)) {
    $error_list[] = t('The domain value must be unique.');
  }
  return $error_list;
}

/**
 * Validate the domain against all correctable errors.
 *
 * Note that we decided not to check for valid TLDs here.
 *
 * @param $subdomain
 *   Domain string to check.
 *
 * @return string
 *   Empty if valid, error message on invalid.
 */
function domain_valid_domain($subdomain) {
  $error_list = array();

  // Check for at least one dot or the use of 'localhost'.
  // Note that localhost can specify a port.
  $localhost_check = explode(':', $subdomain);
  if (substr_count($subdomain, '.') == 0 && $localhost_check[0] != 'localhost') {
    $error_list[] = t('At least one dot (.) is required, except when using <em>localhost</em>.');
  }

  // Check for one colon only.
  if (substr_count($subdomain, ':') > 1) {
    $error_list[] = t('Only one colon (:) is allowed.');
  }
  elseif (substr_count($subdomain, ':') == 1) {
    $parts = explode(':', $subdomain);
    $port = (int) $parts[1];
    if (strcmp($port, $parts[1])) {
      $error_list[] = t('The port protocol must be an integer.');
    }
  }

  // The domain cannot begin or end with a period.
  if (substr($subdomain, 0, 1) == '.') {
    $error_list[] = t('The domain must not begin with a dot (.)');
  }

  // The domain cannot begin or end with a period.
  if (substr($subdomain, -1) == '.') {
    $error_list[] = t('The domain must not end with a dot (.)');
  }

  // Check for valid characters, unless using non-ASCII domains.
  if (!variable_get('domain_allow_non_ascii', FALSE)) {
    $pattern = '/^[a-z0-9\\.\\-:]*$/i';
    if (!preg_match($pattern, $subdomain)) {
      $error_list[] = t('Only alphanumeric characters, dashes, and a colon are allowed.');
    }
  }

  // Check for lower case.
  if ($subdomain != drupal_strtolower($subdomain)) {
    $error_list[] = t('Only lower-case characters are allowed.');
  }

  // Check for 'www' prefix if redirection / handling is enabled under global domain settings.
  // Note that www prefix handling must be set explicitly in the UI.
  // See http://drupal.org/node/1529316 and http://drupal.org/node/1783042
  if (variable_get('domain_www', 0) && substr($subdomain, 0, strpos($subdomain, '.')) == 'www') {
    $error_list[] = t('WWW prefix handling: Domains must be registered without the www. prefix.');
  }

  // Allow modules to alter this behavior.
  drupal_alter('domain_validate', $error_list, $subdomain);

  // Return the errors, if any.
  if (!empty($error_list)) {
    return t('The domain string is invalid for %subdomain:', array(
      '%subdomain' => $subdomain,
    )) . theme('item_list', array(
      'items' => $error_list,
    ));
  }
}

/**
 * Validate the domain against existing domains.
 *
 * @param $subdomain
 *   Domain string to check
 *
 * @return bool
 *   TRUE if unique; FALSE if duplicate.
 */
function domain_unique_domain($subdomain) {
  $count = db_query("SELECT COUNT(domain_id) FROM {domain} WHERE subdomain = :subdomain", array(
    ':subdomain' => $subdomain,
  ))
    ->fetchField();
  return empty($count);
}

/**
 * Get the domains a user is assigned to.
 *
 * @param $account
 *   The user account object.
 * @param $add_roles
 *   A boolean flag indicating whether to add the default role settings to the
 *   user's domains.
 *
 * @return
 *   An array of domains to which the user is assigned, in the format
 *   array($domain_id => $domain_id).
 */
function domain_get_user_domains($account, $add_roles = TRUE) {
  if (empty($account)) {

    // This may happen when creating a new user.
    return array();
  }
  $uid = (int) $account->uid;
  $user_domains = array();
  $result = db_query("SELECT domain_id FROM {domain_editor} WHERE uid = :uid", array(
    ':uid' => $uid,
  ));
  foreach ($result as $data) {
    $user_domains[$data->domain_id] = $data->domain_id;
  }
  if ($add_roles) {
    if (empty($account->roles)) {
      $account->roles = array(
        0 => 'new user',
      );
    }

    // Add the role-based additions.
    $defaults = variable_get('domain_roles', array());
    foreach ($account->roles as $rid => $role) {
      $filter = array();
      if (isset($defaults[$rid])) {
        $filter = array_filter($defaults[$rid]);
      }
      if (!empty($filter)) {

        // If this role is assigned to "All domains"
        if (!empty($filter[DOMAIN_ALL])) {

          // Add all active domains to the user
          foreach (domain_id_list() as $domain_id) {
            $user_domains[$domain_id] = $domain_id;
          }

          // We don't need to loop anymore, as all roles have already been added.
          break;
        }
        else {
          foreach ($filter as $machine_name => $status) {
            if ($status && ($domain_id = domain_load_domain_id($machine_name))) {
              $user_domains[$domain_id] = $domain_id;
            }
          }
        }
      }
    }
  }
  return $user_domains;
}

/**
 * Helper function for passing hook_domain_load() by reference.
 *
 * @param $domain
 *   The domain array defined by domain_lookup().
 * @param $reset
 *   A boolean flag indicating whether to reset the static array or not.
 *
 * @return
 *   The $domain array, modified by reference by hook_domain_load() implementations.
 */
function domain_api($domain, $reset = FALSE) {
  $modules =& drupal_static(__FUNCTION__);

  // There are rare cases where the module_implements() cache is built too early.
  // In these cases, it is the pre-loaded bootstrap modules that can cause
  // issues, so let's be certain to load those.
  if (!isset($modules) || $reset) {
    $modules = module_implements('domain_load');
    if (function_exists('_domain_bootstrap_modules')) {
      $bootstrap = _domain_bootstrap_modules();
      $modules = array_unique(array_merge($modules, $bootstrap));
    }
    elseif (!variable_get('domain_hide_errors')) {

      // Not reporting an error during install makes it easier for scripts.
      $severity = drupal_installation_attempted() ? 'warning' : 'error';
      drupal_set_message(t('Domain module installation is incomplete. See INSTALL.txt and check your settings.php file.'), $severity, FALSE);
    }
  }

  // Now, call the hook invocations.
  if (!empty($modules)) {
    foreach ($modules as $module) {

      // Do not, however, call disabled modules ;-).
      if (module_exists($module)) {

        // Cannot use module_invoke_all() since these are passed by reference.
        $function = $module . '_domain_load';
        module_load_include('inc', $module, $module . '.domain');
        if (function_exists($function)) {
          $function($domain);
        }
      }
    }
  }
  return $domain;
}

/**
 * Set the active domain to something other than the HTTP request.
 *
 * This function is used in cases where you wish to simulate the loading
 * of a domain while on another domain.
 *
 * @param $domain_id
 *   The domain id of the domain to load.
 * @param $bootstrap
 *   Boolean flag that indicates whether to run domain bootstrap load.
 *
 * @return
 *   No return value. The global $_domain value is altered, and domain-specific
 *   data functions are loaded.
 */
function domain_set_domain($domain_id, $bootstrap = FALSE) {
  global $_domain;
  $_domain = domain_load($domain_id);

  // Now re-run the bootstrap.
  if ($bootstrap) {
    _domain_bootstrap_invoke_all('full', $_domain);
  }
}

/**
 * Reset the active domain to its initial version.
 *
 * If $bootstrap is set to TRUE, this function will re-bootstrap
 * Domain Access to restore variables and other settings that
 * may have changed during request execution.
 *
 * If domain_set_domain() invoked TRUE, then this matching
 * function should as well. Otherwise, pass FALSE or empty.
 *
 * @see domain_initial_domain()
 * @see domain_set_domain()
 *
 * @param $bootstrap
 *   Boolean flag that indicates whether to run domain bootstrap load.
 *
 * @return
 *   No return value. The global $_domain value is altered, and domain-specific
 *   data functions are loaded.
 */
function domain_reset_domain($bootstrap = FALSE) {
  $domain = domain_initial_domain();
  if (!empty($domain)) {
    domain_set_domain($domain['domain_id'], $bootstrap);
  }
}

/**
 * Return the currently active domain.
 *
 * This value is stored in a global, but having a function
 * will let us replace that with a static function in D7.
 *
 * This function should be used by all callers who do not
 * need to modify the global variable.
 *
 * @return
 *   An array of data defining the currently active domain.
 */
function domain_get_domain() {
  if (isset($GLOBALS['_domain'])) {
    return $GLOBALS['_domain'];
  }
}

/**
 * Check to see if a redirect to the primary domain is needed.
 *
 * If TRUE, issue a redirect and print a message.
 *
 * @param $msg
 *   The message to print. Optional. If passed, this string must be translated and safe.
 */
function domain_check_primary($msg = 'default') {
  $_domain = domain_get_domain();
  $default = domain_default();
  if ($_domain['domain_id'] != $default['domain_id']) {
    if ($msg == 'default') {
      drupal_set_message(t('You have been redirected: This page must be accessed from the primary domain.'));
    }
    elseif (!empty($msg)) {
      drupal_set_message($msg);
    }
    domain_goto($default);
  }
}

/**
 * Implements hook_domain_load().
 *
 * Adds the home page 'path' and 'site_grant' boolean.
 */
function domain_domain_load(&$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().
 * @return
 *   The base url of the requested domain.
 */
function domain_get_path($domain) {
  global $base_url;
  if (empty($base_url)) {
    return domain_check_scheme($domain['scheme']) . '://' . $domain['subdomain'];
  }

  // Badly malfored HTTP_HOST values can cause problems with PHP < 5.3.3,
  // so we have to suppress error warnings for parse_url here.
  // See php.net/manual/en/function.parse-url.php.
  // See http://drupal.org/node/848856.
  // With PHP > 5.1.2 we can pass a component parameter, too, but Drupal 6
  // still supports PHP 4 and this code was originally tested there.
  $url = array();
  if ($_url = @parse_url($base_url)) {
    $url = $_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_check_scheme($domain['scheme']) . '://' . $domain['subdomain'] . $url['path'];
  return $path;
}

/**
 * Determine an absolute path to the current page.
 *
 * @param $domain
 *   The currently active $domain array, provided by domain_lookup().
 *
 * @return
 *   The absolute url to the current page on the requested domain.
 */
function domain_get_uri($domain) {
  global $base_path;
  $modules = _domain_path_modules();
  if (!empty($modules) && !drupal_is_front_page()) {

    // request_path() does not include base_path, which will be added by the
    // call to url() below.
    $request_uri = request_path();
    $options = array();

    // If needed, let modules modify the path alias.
    // We cannot use URL here because we need the domain_id data.
    // TODO: use url() but pass a domain_id option?
    domain_path($domain['domain_id'], $request_uri, $options, $_GET['q']);
    $request_uri = base_path() . $request_uri;
  }
  else {
    $request_uri = request_uri();
  }
  $path = domain_check_scheme($domain['scheme']) . '://' . $domain['subdomain'] . $request_uri;
  return $path;
}

/**
 * Ensure that the scheme value has not been hacked.
 *
 * Note that Domain Access only supports HTTP and HTTPS.
 * Other protocols will be removed.
 *
 * @param $scheme
 *   The request protocol for the requested domain.
 *
 * @return
 *  Either 'http' or 'https'.
 */
function domain_check_scheme($scheme) {
  if ($scheme != 'https') {
    $scheme = 'http';
  }
  return $scheme;
}

/**
 * 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) {
  $_domain = domain_get_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_node_load().
 */
function domain_node_load($nodes, $types) {

  // Get the domain data.
  $domains = domain_get_node_domains($nodes);
  foreach ($nodes as $node) {

    // Cannot load if the node has not been created yet.
    if (!isset($node->nid)) {
      continue;
    }

    // Get default settings.
    $defaults = domain_get_node_defaults($node->type);

    // Append the domain grants to the node for editing.
    $nodes[$node->nid]->domains = isset($domains[$node->nid]['domain_id']) ? $domains[$node->nid]['domain_id'] : array();

    // If the node is not assigned, grant to all domains to prevent errors.
    $nodes[$node->nid]->domain_site = isset($domains[$node->nid]['domain_site']) ? $domains[$node->nid]['domain_site'] : $defaults['domain_site'];
    $nodes[$node->nid]->subdomains = array();
    if (!empty($nodes[$node->nid]->domain_site)) {
      $nodes[$node->nid]->subdomains[] = t('All affiliates');
    }
    if (!empty($nodes[$node->nid]->domains)) {
      foreach ($nodes[$node->nid]->domains as $gid) {
        $domain = domain_lookup($gid);
        $nodes[$node->nid]->subdomains[] = $domain['sitename'];
      }
    }
    else {
      $nodes[$node->nid]->subdomains[] = t('This node is not assigned to a domain.');
    }
  }
}

/**
 * Implements hook_node_view()
 *
 * Display debugging information for a node.
 */
function domain_node_view($node, $view_mode) {
  if (empty($node->nid) || !in_array($view_mode, array(
    'full',
    'teaser',
  ))) {
    return;
  }
  $output = '';
  if (variable_get('domain_debug', 0) && user_access('set domain access')) {
    if (!empty($node->subdomains)) {
      $items = array();
      foreach ($node->subdomains as $name) {
        $items[] = check_plain($name);
      }
      $output .= theme('item_list', array(
        'items' => $items,
        'title' => t('Assigned domains'),
      ));
    }
    if (!empty($node->editors)) {
      $items = array();
      foreach ($node->editors as $name) {
        $items[] = check_plain($name);
      }
      $output .= theme('item_list', array(
        'items' => $items,
        'title' => t('Editors'),
      ));
    }
    if (empty($output)) {
      $output = t('This node is not assigned to a domain.');
    }
    $node->content['domain'] = array(
      '#markup' => $output,
    );
  }
}

/**
 * Implements hook_node_delete().
 */
function domain_node_delete($node) {
  db_delete('domain_access')
    ->condition('nid', $node->nid)
    ->execute();
}

/**
 * Implements hook_node_presave().
 *
 * Allows devel generate to add domains.
 */
function domain_node_presave($node) {
  if (empty($node->devel_generate['domains'])) {
    return;
  }

  // Build $domains array based on domains checked in the generate form
  // and shuffle for randomization
  $checked = array_filter($node->devel_generate['domains']);
  if (empty($checked)) {
    return;
  }
  shuffle($checked);
  $domains = array_combine(array_values($checked), array_values($checked));

  // Add the domains and supporting data to the node
  if (!empty($domains)) {

    // Remove some domains from the shuffled array (if more than one domain
    // is chosen) for randomization (-1 guarantees at least one domain).
    if (count($domains) > 1) {
      $howmany = rand(0, count($domains) - 1);
      for ($i = 0; $i < $howmany; $i++) {
        array_pop($domains);
      }
    }

    // Add the domains to the node and grab the first domain as the source.
    // The source is random because the array has been shuffled.
    $node->domains = $domains;
    $node->domain_source = current($domains);

    // domain_site is set to TRUE or FALSE based on "all", "never" or "random flag"
    $node->domain_site = $node->devel_generate['domain_site'] == 'all' ? 1 : ($node->devel_generate['domain_site'] == 'random' ? rand(0, 1) == 1 : 0);

    // Set subdomains according to the domains in $domains
    $node->domains = array();
    foreach ($domains as $id) {
      $node->domains[$id] = $id;
    }
  }
}

/**
 * Implements hook_node_insert().
 */
function domain_node_insert($node) {

  // Any function that tries to call domain_get_node_domains() or
  // domain_get_node_match() needs this data.
  domain_node_access_records($node);
}

/**
 * Implements hook_node_update().
 */
function domain_node_update($node) {
  domain_node_access_records($node);
}

/**
 * Implements hook_node_type_delete().
 */
function domain_node_type_delete($info) {
  variable_del('domain_node_' . $info->type);
}

/**
 * Get the best matching domain for a node link.
 *
 * @param $nid
 *   The node id.
 *
 * @return
 *   The domain array for the best matching domain for links to this node.
 */
function domain_get_node_match($nid) {
  $domain =& drupal_static(__FUNCTION__, array());
  if (isset($domain[$nid])) {
    return $domain[$nid];
  }

  // Load the domain data for this node -- but only take the first match.
  $id = db_query("SELECT gid FROM {domain_access} WHERE nid = :nid AND realm = :realm ORDER BY gid", array(
    ':nid' => $nid,
    ':realm' => 'domain_id',
  ))
    ->fetchField();

  // If a match was found, return it. Otherwise, we may be saving a node
  // in which case, let it fall through. See http://drupal.org/node/624360.
  $source = NULL;
  if ($id !== FALSE) {
    $source = domain_lookup($id);
  }
  else {
    $source = domain_get_domain();
  }

  // Allow other modules to intervene.
  drupal_alter('domain_source', $source, $nid);
  $domain[$nid] = $source;
  return $source;
}

/**
 * Get all possible published URLs pointing to a node.
 *
 * @param $node
 *   A node object.
 *
 * @return array
 *   An array of absolute URLs keyed by domain_id, with the canonical id
 *   as the first element of the array.
 */
function domain_get_content_urls($node) {
  $urls = array();

  // If not set, return normal absolute url.
  if (empty($node->domains)) {
    $options['absolute'] = TRUE;
    $urls[] = url('node/' . $node->id, $options);
  }
  $weight = -100;
  foreach ($node->domains as $domain_id) {
    $domain = domain_load($domain_id);
    if (!$domain['valid']) {
      continue;
    }

    // Calculate the proper path to the node.
    $path = drupal_get_path_alias('node/' . $node->nid, $node->language);
    $options['language'] = $node->language;
    domain_path($domain_id, $path, $options, $path);
    $url = $domain['path'] . $path;

    // Order domains by weight.
    if ($domain['weight'] > $weight) {
      $new[$domain_id] = $url;
      $urls = $new + $urls;
    }
    else {
      $urls[$domain_id] = $url;
    }
    $weight = $domain['weight'];
  }

  // Determine the canonical URL and make it the first item.
  // This only comes into play if domain source is used.
  // Else we assume the first item in the array is canonical.
  if (isset($node->domain_source) && isset($urls[$node->domain_source])) {
    $canonical[$node->domain_source] = $urls[$node->domain_source];
    unset($urls[$node->domain_source]);
    $urls = $canonical + $urls;
  }
  return $urls;
}

/**
 * Allow the lookup of path rewrites.
 *
 * Note that unlike domain_get_node_match(), this function assumes
 * that all links will be written to the current domain.
 *
 * @param $path
 *   The link path.
 *
 * @return
 *   The domain array for the best matching domain for links to this node.
 */
function domain_get_path_match($path) {
  $_domain = domain_get_domain();

  // When using modules that alter the path, this can be called before the
  // domain path is loaded. See http://drupal.org/node/1193338.
  if (!isset($_domain['path'])) {
    $_domain = domain_lookup($_domain['domain_id'], NULL, TRUE);
  }
  $source = $_domain;
  drupal_alter('domain_source_path', $source, $path);
  return $source;
}

/**
 * Get the domains for multiple matches, mimicking node_load().
 *
 * @param $nodes
 *   An array of nodes, keyed by node id, or a single node id.
 *
 * @return
 *   An array of data, keyed by node id, or a single array for a single node.
 *   The data array, consists of two parts. 'domain_id' is an array of active domain ids.
 *   'domain_site' is a TRUE/FALSE boolean indicating affiliate status.
 */
function domain_get_node_domains($nodes) {
  $lookup =& drupal_static(__FUNCTION__, array());

  // Ensure we form our data properly.
  if (!is_array($nodes)) {
    $node_ids[$nodes] = $nodes;
    $array = FALSE;
  }
  else {
    $node_ids = $nodes;
    $array = TRUE;
  }

  // If not an array, just return the requested data.
  if (!$array && isset($lookup[$nodes])) {
    return $lookup[$nodes];
  }

  // Set the proper value for the node, but include a default.
  $domains = array(
    'domain_id' => array(),
    'domain_site' => FALSE,
  );
  $records = array();
  $result = db_query("SELECT nid, gid, realm FROM {domain_access} WHERE nid IN (:nid)", array(
    ':nid' => array_keys($node_ids),
  ));

  // While this should return records for every node, we cannot guarantee success.
  foreach ($result as $data) {
    if ($data->realm == 'domain_id') {
      $records[$data->nid]['domain_id'][$data->gid] = $data->gid;
    }
    elseif ($data->realm == 'domain_site') {
      $records[$data->nid]['domain_site'] = TRUE;
    }
  }

  // Run through the nodes and ensure they have proper data.
  foreach ($node_ids as $nid => $value) {
    $lookup[$nid] = $domains;
    foreach (array(
      'domain_id',
      'domain_site',
    ) as $key) {
      if (isset($records[$nid][$key])) {
        $lookup[$nid][$key] = $records[$nid][$key];
      }
    }
  }

  // Return all data or just a single node?
  if ($array) {
    return $lookup;
  }
  if (isset($lookup[$nodes])) {
    return $lookup[$nodes];
  }
  return $domains;
}

/**
 * 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.
 *
 * In Drupal 7, you may modify these grants in your own module, through
 * the function hook_node_grants_alter().
 *
 * @see domain_strict_node_grants_alter()
 * @link http://api.drupal.org/api/function/hook_node_grants_alter/7
 */
function domain_node_grants($account, $op) {
  $_domain = domain_get_domain();
  $grants = array();

  // In some edge cases, the $account may not have domain information loaded,
  // so get it.
  domain_user_set($account);

  // 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 (!empty($_domain['site_grant'])) {
      $grants['domain_site'][] = 0;
    }

    // Grant based on active domain.
    if (isset($_domain['domain_id'])) {
      $grants['domain_id'][] = $_domain['domain_id'];
    }

    // 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 (domain_grant_all()) {
      $grants = array(
        'domain_all' => array(
          0,
        ),
      );
    }
  }
  else {
    $domains = $account->domain_user;
    $perm = 'delete domain content';
    if ($op == 'update') {
      $perm = 'edit domain content';
    }
    if (user_access($perm, $account)) {
      if (!empty($domains)) {
        foreach ($domains as $id) {
          if (abs($id) > 0) {
            if ($id > 0) {
              $grants['domain_id'][] = $id;
            }
            else {
              $grants['domain_id'][] = 0;
            }
          }
        }
      }
    }
  }

  // If the user may view unpublished content, then add those grants.
  if (!empty($grants['domain_id']) && user_access('view unpublished domain content', $account)) {
    foreach ($grants['domain_id'] as $id) {
      $domains = $account->domain_user;
      if (!empty($domains[$id])) {
        $grants['domain_unpublished'][] = $id;
      }
    }
  }
  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.
 *
 * Developers: if you modify these grants with hook_node_access_records_alter(),
 * you may also need to call _domain_store_grants() to update the
 * {domain_access} table properly.
 *
 * @see _domain_store_grants()
 * @link http://api.drupal.org/api/function/hook_node_access_records_alter/7
 *
 */
function domain_node_access_records($node) {

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

  // Check to see if the node domains were set properly.
  // If not, we are dealing with an automated node process, which
  // means we have to add the logic from hook_form_alter() here.
  if (!isset($node->domain_site)) {

    // We came from a separate source, so let's set the proper defaults.
    $defaults = domain_get_node_defaults($node->type, array(
      domain_get_domain(),
    ));
    $node->domain_site = $defaults['domain_site'];
    $node->domains = $defaults['domain_id'];
  }

  // 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 publishing' in domain_form_alter().
  if (!empty($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 (!empty($node->domain_site)) {
    $grants[] = array(
      'realm' => 'domain_site',
      'gid' => 0,
      'grant_view' => $node->status,
      'grant_update' => 0,
      'grant_delete' => 0,
      'priority' => 0,
    );
  }

  // Set the domain-specific grants.
  if (!empty($node->domains)) {
    $domains = array_filter($node->domains);
  }

  // Set the proper realm, for use with unpublished nodes.
  $realm = 'domain_id';
  if (!$node->status) {
    $realm = 'domain_unpublished';
  }

  // Set the specified domains.
  if (!empty($domains)) {
    foreach (array_filter($node->domains) as $key => $value) {
      $grants[] = array(
        'realm' => $realm,
        'gid' => $key,
        'grant_view' => 1,
        'grant_update' => 1,
        'grant_delete' => 1,
        'priority' => 0,
      );
    }
  }
  else {
    $default = domain_default();
    $grants[] = array(
      'realm' => $realm,
      'gid' => $default['domain_id'],
      'grant_view' => 1,
      'grant_update' => 1,
      'grant_delete' => 1,
      'priority' => 0,
    );
  }

  // Store our records in the {domain_access} table.
  _domain_store_grants($node->nid, $grants);
  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_store_grants($nid, $grants = array()) {

  // Store the grants records.
  if ($nid > 0 && !empty($grants)) {
    db_delete('domain_access')
      ->condition('nid', $nid)
      ->execute();
    $values = array();
    foreach ($grants as $grant) {

      // Always store unpublished nodes under domain_id.
      if ($grant['realm'] == 'domain_unpublished') {
        $grant['realm'] = 'domain_id';
      }
      $values[] = array(
        'nid' => $nid,
        'gid' => $grant['gid'],
        'realm' => $grant['realm'],
      );
    }
    $query = db_insert('domain_access')
      ->fields(array(
      'nid',
      'gid',
      'realm',
    ));
    foreach ($values as $record) {
      $query
        ->values($record);
    }
    $query
      ->execute();
  }

  // Reset the node-related domain static variables.
  drupal_static_reset('domain_get_node_domains');
  drupal_static_reset('domain_get_node_match');

  // Ensure that our default grant is present.
  domain_set_default_grant();
}

/**
 * Ensure that the 'domain_all' grant is present.
 *
 * @param $reset
 *   A boolean flag indicating whether to reset the static variable or not.
 */
function domain_set_default_grant($reset = FALSE) {
  $check =& drupal_static(__FUNCTION__, NULL);
  if (is_null($check) || $reset) {
    $check = (bool) db_query_range("SELECT 1 FROM {node_access} WHERE realm = :realm AND gid = :gid", 0, 1, array(
      ':realm' => 'domain_all',
      ':gid' => 0,
    ))
      ->fetchField();
    if (empty($check)) {
      db_insert('node_access')
        ->fields(array(
        'nid' => 0,
        'gid' => 0,
        'realm' => 'domain_all',
        'grant_view' => 1,
        'grant_update' => 0,
        'grant_delete' => 0,
      ))
        ->execute();
    }
  }
}

/**
 * Implements hook_node_access().
 *
 * Splits the access checks by operation.
 */
function domain_node_access($node, $op, $account) {
  domain_user_set($account);
  if (empty($account->domain_user)) {
    return NODE_ACCESS_IGNORE;
  }
  $function = "domain_node_access_{$op}";
  if (function_exists($function)) {
    $type = is_string($node) ? $node : $node->type;
    return $function($type, $node, $op, $account);
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Checks if a user can view unpublished nodes assigned to her domain(s).
 *
 * @see domain_node_access().
 */
function domain_node_access_view($type, $node, $op, $account) {
  domain_user_set($account);
  if (!empty($node->status)) {
    return NODE_ACCESS_IGNORE;
  }
  if (empty($node->domains) || empty($account->domain_user)) {
    return NODE_ACCESS_IGNORE;
  }
  if (!user_access('view unpublished domain content', $account)) {
    return NODE_ACCESS_IGNORE;
  }

  // The actual access check.
  foreach ($node->domains as $key => $value) {
    if (!empty($account->domain_user[$key])) {
      return NODE_ACCESS_ALLOW;
    }
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Checks if a user can create content of a specific type on the current domain.
 *
 * @see domain_node_access().
 */
function domain_node_access_create($type, $node, $op, $account) {
  domain_user_set($account);
  $_domain = domain_get_domain();
  if (!isset($account->domain_user[$_domain['domain_id']])) {
    return NODE_ACCESS_IGNORE;
  }

  // Build the permission string.
  $permission = "{$op} {$type} content on assigned domains";

  // Run the access check.
  if (user_access($permission, $account)) {
    return NODE_ACCESS_ALLOW;
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Checks if a user can edit content of a specific type based on domain.
 *
 * @see domain_node_access().
 */
function domain_node_access_update($type, $node, $op, $account) {
  domain_user_set($account);

  // The node must be assigned to a domain to continue.
  if (empty($node->domains)) {
    return NODE_ACCESS_IGNORE;
  }
  else {
    $ignore = TRUE;
    foreach ($node->domains as $domain_id) {
      if (isset($account->domain_user[$domain_id])) {
        $ignore = FALSE;
        break;
      }
    }
    if ($ignore) {
      return NODE_ACCESS_IGNORE;
    }
  }

  // Build the permission string.
  $permission = "{$op} {$type} content on assigned domains";

  // Run the access check.
  if (user_access($permission, $account)) {
    return NODE_ACCESS_ALLOW;
  }

  // If the check fails, do nothing.
  return NODE_ACCESS_IGNORE;
}

/**
 * Checks if a user can delete content of a specific type based on domain.
 *
 * This is just a wrapper around the update function, which uses the same logic.
 *
 * @see domain_node_access().
 */
function domain_node_access_delete($type, $node, $op, $account) {
  return domain_node_access_update($type, $node, $op, $account);
}

/**
 * Upon enabling this module, store the default view grant
 * in the {node_access} table. Then it assigns all users to
 * the primary domain.
 *
 * TODO: revisit the need for the data loop.
 */
function domain_enable() {

  // Set the default 'domain_all' grant for special pages.
  domain_set_default_grant(TRUE);

  // Get the default domain.
  $default = domain_default();

  // Thanks to the new way that batch processing of node grants is handled, we have to
  // manually define our records if none are present.
  $count = (bool) db_query_range("SELECT 1 FROM {domain_access}", 0, 1)
    ->fetchField();
  if (empty($count)) {
    $rule = DOMAIN_INSTALL_RULE;
    $site = DOMAIN_SITE_GRANT;
    $values = array();
    $result = db_query("SELECT nid FROM {node}");
    foreach ($result as $node) {
      if (!empty($site)) {
        $values[] = array(
          'nid' => $node->nid,
          'gid' => 0,
          'realm' => 'domain_site',
        );
      }
      if (!empty($rule)) {
        $values[] = array(
          'nid' => $node->nid,
          'gid' => $default['domain_id'],
          'realm' => 'domain_id',
        );
      }
    }
    $query = db_insert('domain_access')
      ->fields(array(
      'nid',
      'gid',
      'realm',
    ));
    foreach ($values as $record) {
      $query
        ->values($record);
    }
    $query
      ->execute();
  }

  // Add users to the {domain_editor} table, but skip user 0.
  if (!DOMAIN_ASSIGN_USERS) {
    return;
  }
  $result = db_query("SELECT uid FROM {users} WHERE uid > 0");
  $values = array();
  foreach ($result as $account) {
    $check = (bool) db_query_range("SELECT COUNT(uid) FROM {domain_editor} WHERE uid = :uid", 0, 1, array(
      ':uid' => $account->uid,
    ))
      ->fetchField();
    if (empty($check)) {
      $values[] = array(
        'domain_id' => $default['domain_id'],
        'uid' => $account->uid,
      );
    }
  }
  $query = db_insert('domain_editor')
    ->fields(array(
    'domain_id',
    'uid',
  ));
  foreach ($values as $record) {
    $query
      ->values($record);
  }
  $query
    ->execute();
}

/**
 * Implements hook_form_alter().
 *
 * This function is crucial, as it appends our node access elements to the node
 * edit form. For users without the "set domain access" permission, this happens
 * silently.
 */
function domain_form_alter(&$form, &$form_state, $form_id) {

  // There are forms that we never want to alter, and they are passed here.
  $forms = module_invoke_all('domain_ignore');
  if (in_array($form_id, $forms)) {
    return;
  }

  // Set a message if we are on an admin page.
  domain_warning_check($form_id);

  // If SEO is turned on, then form actions need to be absolute paths
  // to the currently active domain.  See http://drupal.org/node/196217.
  $seo = variable_get('domain_seo', 0);
  if ($seo && isset($form['#action'])) {

    // We cannot use the global domain here, since it can be modified.
    $domain = domain_initial_domain();
    $action = parse_url($form['#action']);
    if (isset($action['query'])) {
      $action['path'] .= '?';
    }
    else {
      $action['query'] = '';
    }

    // We cannot reset this if it has already been set by another module.
    // See http://drupal.org/node/306551
    if (empty($action['host'])) {
      $form['#action'] = $domain['scheme'] . '://' . $domain['subdomain'] . $action['path'] . $action['query'];
    }
  }

  // Apply to all node editing forms only.
  if (empty($form['#node_edit_form'])) {
    return;
  }

  // Grab the globals we need.
  global $user;
  domain_user_set($user);
  $_domain = domain_get_domain();

  // Get the default assigned domains.
  $defaults = domain_get_node_defaults($form['#node']->type);

  // How is core content handled for this site?
  // In D7, type handling is strict, so make this value 0 or 1.
  // @TODO: clean up DOMAIN_INSTALL handling.
  $default_domain_site = (int) $defaults['domain_site'];

  // Some options will be passed as hidden values, we need to run some checks on those.
  if (isset($form['#node']->nid) && !empty($form['#node']->domains)) {
    $raw = $form['#node']->domains;
  }
  else {
    $raw = $defaults['domain_id'];
  }
  $options = array();

  // Get the display format of the form element.
  $format = domain_select_format();
  foreach (domain_domains() as $data) {

    // The domain must be valid.
    if ($data['valid'] || user_access('access inactive domains')) {

      // Checkboxes must be filtered, select lists should not.
      $options[$data['domain_id']] = empty($format) ? check_plain($data['sitename']) : $data['sitename'];
    }
  }

  // If the user is a site admin, show the form, otherwise pass it silently.
  if (user_access('set domain access')) {
    $form['domain'] = array(
      '#type' => 'fieldset',
      '#title' => t('Domain access options'),
      '#collapsible' => TRUE,
      '#collapsed' => variable_get('domain_collapse_options', 0),
    );

    // Display in vertical tab group, if required
    if (variable_get('domain_vertical_tab', 0)) {
      $form['domain']['#group'] = 'additional_settings';
      $form['domain']['#attributes'] = array(
        'class' => array(
          'domain-access-options-form',
        ),
      );
      $form['domain']['#attached'] = array(
        'js' => array(
          drupal_get_path('module', 'domain') . '/domain.node.js',
          array(
            'data' => array(
              'domain' => array(
                'fieldType' => $format,
              ),
            ),
            'type' => 'setting',
          ),
        ),
      );
    }
    $form['domain']['domain_site'] = array(
      '#type' => 'checkbox',
      '#title' => t('Send to all affiliates'),
      '#required' => FALSE,
      '#description' => t('Select if this content can be shown to all affiliates. This setting will override the options below, but you must still select a domain that "owns" this content.'),
      '#default_value' => isset($form['#node']->domain_site) ? $form['#node']->domain_site : $default_domain_site,
    );
    $form['domain']['domains'] = array(
      '#type' => empty($format) ? 'checkboxes' : 'select',
      '#title' => t('Publish to'),
      '#options' => $options,
      '#required' => TRUE,
      '#description' => t('Select which affiliates can access this content.'),
      '#default_value' => isset($form['#node']->domains) ? $form['#node']->domains : $defaults['domain_id'],
    );
    if ($format) {
      $form['domain']['domains']['#multiple'] = TRUE;
      $form['domain']['domains']['#size'] = count($options) > 10 ? 10 : count($options);
    }
  }
  else {
    $action = domain_form_permission_check();
    if (!empty($action)) {

      // hook_user() has not run, so get the domain data for this user.
      $user->domain_user = domain_get_user_domains($user);
      $user_domains = array();
      $default_options = array();
      $user_options = array();
      $raw_options = array();
      if (!empty($user->domain_user)) {
        foreach ($user->domain_user as $key => $value) {
          if (abs($value) > 0) {
            $user_domains[] = $value;
          }
        }

        // Set the best match for the user's primary domain.
        // If they are a member of the current domain, use that one.
        if (in_array($_domain['domain_id'], $user_domains)) {
          $first_domain = $_domain['domain_id'];
        }
        else {
          $first_domain = current($user_domains);
        }
        foreach ($options as $key => $value) {
          if (in_array($key, $user_domains)) {
            $user_options[$key] = $value;
          }
        }
      }

      // Raw data checks for published nodes.
      foreach ($raw as $key => $value) {
        if (in_array($value, $user_domains)) {
          $default_options[] = $value;
        }
        else {
          $raw_options[] = $value;
        }
      }

      // Act on the behavior desired by the site admin.
      switch ($action) {

        // 1 == go to the default domain.
        case 1:
          $root = domain_default();
          if ($root['domain_id'] != $_domain['domain_id']) {
            domain_goto($root);
          }
          break;

        // 2 == go to the user's assigned domain.
        case 2:
          $domain = domain_lookup($first_domain);

          // If the domain is invalid, go to the primary domain.
          if ($domain == -1 || empty($domain['valid']) && !user_access('access inactive domains')) {
            domain_goto(domain_default());
          }
          elseif ($domain['domain_id'] != $_domain['domain_id']) {
            domain_goto($domain);
          }
          break;

        // 3 == show checkboxes of available domains.
        case 3:

          // If the user has no available domains, then they cannot post.
          if (empty($user_options)) {
            drupal_access_denied();
            drupal_exit();
          }
          $form['domain'] = array(
            '#type' => 'fieldset',
            '#title' => t('Affiliate publishing options'),
            '#collapsible' => TRUE,
            '#collapsed' => variable_get('domain_collapse_options', 0),
          );

          // Display in vertical tab group, if required
          if (variable_get('domain_vertical_tab', 0)) {
            $form['domain']['#group'] = 'additional_settings';
            $form['domain']['#attributes'] = array(
              'class' => array(
                'domain-access-options-form',
              ),
            );
            $format = domain_select_format();
            $form['domain']['#attached'] = array(
              'js' => array(
                drupal_get_path('module', 'domain') . '/domain.node.js',
                array(
                  'data' => array(
                    'domain' => array(
                      'fieldType' => $format,
                    ),
                  ),
                  'type' => 'setting',
                ),
              ),
            );
          }

          // We must preserve publishing options that the user cannot access, but only for
          // existing nodes.
          if (!empty($form['#node']->nid)) {
            $raw = $raw_options;
          }
          else {
            $raw = array();
          }

          // If the raw options are being passed, then no input is technically required.
          empty($raw) ? $required = TRUE : ($required = FALSE);
          $form['domain']['domains'] = array(
            '#type' => empty($format) ? 'checkboxes' : 'select',
            '#title' => t('Publish to'),
            '#options' => $user_options,
            '#required' => $required,
            '#description' => t('Select which affiliates can access this content.'),
            '#default_value' => isset($form['#node']->domains) ? $form['#node']->domains : $default_options,
          );
          if ($format) {
            $form['domain']['domains']['#multiple'] = TRUE;
            $form['domain']['domains']['#size'] = count($user_options) > 10 ? 10 : count($user_options);
          }

          // Show the options that cannot be changed.
          $list = array();
          if (!empty($form['#node']->domain_site)) {
            $list[]['data'] = t('All affiliates');
          }
          if (!empty($raw)) {
            foreach ($raw as $did) {
              $raw_domains = domain_lookup($did);
              $list[]['data'] = check_plain($raw_domains['sitename']);
            }
          }
          if (!empty($list)) {
            $form['domain']['domains_notes'] = array(
              '#type' => 'item',
              '#title' => t('Publishing status'),
              '#markup' => theme('item_list', array(
                'items' => $list,
              )),
              '#description' => t('This content has also been published to these affiliates.'),
            );
          }
          break;
      }
    }

    // These form elements are hidden from non-privileged users, by design.
    $form['domain_site'] = array(
      '#type' => 'value',
      '#value' => isset($form['#node']->domain_site) ? $form['#node']->domain_site : $default_domain_site,
    );

    // Domains that have been assigned and cannot be changed.
    $form['domains_raw'] = array(
      '#type' => 'value',
      '#value' => $raw,
    );
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds configurable default settings to the node type edit form.
 *
 */
function domain_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  $options = array(
    DOMAIN_ALL => t('All domains'),
    DOMAIN_ACTIVE => t('Author\'s currently active domain'),
  );
  foreach (domain_domains() as $key => $value) {
    $options[$value['machine_name']] = $value['sitename'];
  }
  $default_values = domain_default_node_access_settings($form['#node_type']->type);
  $form['domain'] = array(
    '#type' => 'fieldset',
    '#title' => t('Domain access settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#group' => 'additional_settings',
    '#access' => user_access('set domain access'),
  );
  $form['domain']['domain_node'] = array(
    '#title' => t('Publish to'),
    '#description' => t('You may set default domain publishing options when new content of this type will be created. When you publish to <em>All domains</em>, you can additionally define domain memberships.'),
    '#type' => 'checkboxes',
    '#tree' => TRUE,
    '#options' => $options,
    '#default_value' => $default_values,
  );
}

/**
 * Update the default domain's sitename.
 */
function domain_form_system_site_information_settings_alter(&$form, &$form_state) {
  if (variable_get('domain_sitename_override', 1)) {
    $form['#submit'][] = 'domain_form_sitename_submit';
  }
}

/**
 * FormsAPI submit handler to track site name changes.
 */
function domain_form_sitename_submit($form, &$form_state) {

  // When using Domain Settings, we cannot save this value.
  if (isset($form['domain_settings'])) {
    return;
  }
  db_update('domain')
    ->condition('domain_id', 0)
    ->fields(array(
    'sitename' => $form_state['values']['site_name'],
  ))
    ->execute();
  drupal_set_message(t('Primary domain settings updated.'));
}

/**
 * Check a user's permissions for displaying the Domain form on nodes.
 *
 * This sets the hierarchy of user form permissions for those without 'set
 * domain access'. Note that the most permissive permission wins.
 */
function domain_form_permission_check() {
  $perms = array(
    'publish from default domain' => 1,
    'publish from assigned domain' => 2,
    'publish to any assigned domain' => 3,
  );

  // By default, we will hide the form by returning NULL.
  $action = NULL;
  foreach ($perms as $perm => $value) {
    if (user_access($perm)) {
      $action = $value;
    }
  }
  return $action;
}

/**
 * Add settings to devel generate module.
 */
function domain_form_devel_generate_content_form_alter(&$form, &$form_state) {
  $form['submit']['#weight'] = 10;
  $form['domain'] = array(
    '#type' => 'fieldset',
    '#title' => t('Domain Access Options'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#weight' => 9,
  );
  $form['domain']['domain_site'] = array(
    '#type' => 'select',
    '#title' => t('Send to all affiliates'),
    '#options' => array(
      'none' => t('Never'),
      'all' => t('Always'),
      'random' => t('Randomly decide'),
    ),
    '#description' => t('If you choose "always" or "randomly" you must select at least one domain below.'),
  );

  // Get the display format of the form element.
  $format = domain_select_format();
  foreach (domain_domains() as $data) {

    // The domain must be valid.
    if ($data['valid'] || user_access('access inactive domains')) {

      // Checkboxes must be filtered, select lists should not.
      $options[$data['domain_id']] = empty($format) ? check_plain($data['sitename']) : $data['sitename'];
    }
  }
  $form['domain']['domains'] = array(
    '#type' => empty($format) ? 'checkboxes' : 'select',
    '#title' => t('Publish to'),
    '#options' => $options,
    '#description' => t('Generated content will be accessible on any or all of the domains checked above.'),
  );
  if ($format) {
    $form['domain']['domains']['#multiple'] = TRUE;
    $form['domain']['domains']['#size'] = count($options) > 10 ? 10 : count($options);
  }
}

/**
 * Activate the hidden grant for searches.
 *
 * @param $reset
 *   A boolean flag indicating whether to reset the static variable or not.
 *
 * @return
 *   TRUE or FALSE, depending on whether the grants should be executed for this
 *   page.
 */
function domain_grant_all($reset = FALSE) {
  $grant =& drupal_static(__FUNCTION__);
  $options = array();
  if (!isset($grant) || $reset) {
    $grant = FALSE;

    // Search is the easy case, so we check it first.
    if (variable_get('domain_search', 0) && arg(0) == 'search') {
      $options['search'] = TRUE;
      $grant = TRUE;
    }

    // On cron runs, we normally have to disable Domain Access.  See
    // http://drupal.org/node/197488.
    // We also check XMLRPC. See http://drupal.org/node/775028.
    if (!$grant) {
      $ref = explode('/', request_uri());
      $script = array_pop($ref);

      // Note that cron can be invoked from services other than cron.php,
      // so we check for that case and set the script variable manually.
      if (variable_get('domain_cron_rule', 1) && domain_cron_status()) {
        $options['script'] = 'cron.php';
        $grant = TRUE;
      }
      elseif (variable_get('domain_xmlrpc_rule', 0) && $script == 'xmlrpc.php') {
        $options['script'] = $script;
        $grant = TRUE;
      }
    }
    if (!$grant) {

      // We check the paths registered by the special pages setting.
      $pages = variable_get('domain_grant_all', "user/*/track");
      $options['pages'] = $pages;
      $regexp = '/^(' . preg_replace(array(
        '/(\\r\\n?|\\n)/',
        '/\\\\\\*/',
        '/(^|\\|)\\\\<front\\\\>($|\\|)/',
      ), array(
        '|',
        '.*',
        '\\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\\2',
      ), preg_quote($pages, '/')) . ')$/';

      // Compare with the internal and path alias (if any).
      $page_match = preg_match($regexp, $_GET['q']);
      if (!$page_match && function_exists('drupal_get_path_alias')) {
        $path = drupal_get_path_alias($_GET['q']);
        if ($path != $_GET['q']) {
          $page_match = preg_match($regexp, $path);
        }
      }
      if ($page_match) {
        $options['page_match'] = TRUE;
        $grant = TRUE;
      }
    }
  }

  // Allow other modules to change the defaults.
  drupal_alter('domain_grant_all', $grant, $options);
  return $grant;
}

/**
 * Implements hook_domain_cron_queue_info_alter().
 *
 * Uses Queue API info hook to detect if cron is running.
 *
 * @see https://drupal.org/node/1851400
 */
function domain_cron_queue_info_alter(&$queues) {
  domain_cron_status(TRUE);
}

/**
 * Gets the status of cron.
 *
 * @param $status
 *   Pass a value to set status. No value to retrieve status.
 *
 * @return
 *   Boolean TRUE or FALSE.
 */
function domain_cron_status($status = NULL) {
  $return =& drupal_static(__FUNCTION__, FALSE);
  if (is_null($status)) {
    return $return;
  }
  $return = $status;
  return $return;
}

/**
 * Implements hook_modules_enabled().
 */
function domain_modules_enabled() {
  domain_bootstrap_register();
  domain_check_for_update();
}

/**
 * Implements hook_modules_disabled().
 */
function domain_modules_disabled() {
  domain_bootstrap_register();
}

/**
 * Register the modules needed to load during bootstrap.
 *
 * Stores results in the 'domain_bootstrap_modules' variable.
 */
function domain_bootstrap_register() {
  $modules = array();
  $lookup = module_implements('domain_bootstrap_lookup');
  $full = module_implements('domain_bootstrap_full');
  $modules = array_merge($lookup, $full);
  variable_set('domain_bootstrap_modules', $modules);
}

/**
 * Resolve an HTTP_HOST to a registered domain.
 *
 * Tries to match the current HTTP_HOST to a domain in the {domain} table and
 * returns a respective domain_id.
 *
 * @param $name
 *   The domain name to match against. Optional.
 *
 * @return
 *   An array containing a domain_id matching the current domain name.
 */
function domain_resolve_host($name = '') {
  if (empty($name)) {
    $name = domain_request_name();
  }
  return domain_lookup_simple($name);
}

/**
 * Determines current, fully qualified domain name.
 *
 * Relies on $_SERVER['HTTP_HOST'] being set. Note that this value has already
 * been security checked by Drupal core. Otherwise, we never get this far.
 *
 * @see conf_init()
 *
 * @return
 * The current (host) domain name as a String.
 */
function domain_request_name() {

  // An empty host (provided by some bots) always fails, so don't bother
  // trying to resolve it.
  if (empty($_SERVER['HTTP_HOST'])) {
    $domain = domain_default(FALSE, FALSE);
    return $domain['subdomain'];
  }

  // Otherwise, trim and return the HTTP_HOST value for checking against
  // registered domains. Note that we lower case this, since the RFC states
  // that EXAMPLE.com == example.com.
  return strtolower(rtrim($_SERVER['HTTP_HOST']));
}

/**
 * Redirect a request to an invalid domain.
 *
 * We were sent here from domain_init() because the user cannot
 * view the requested domain.
 *
 * Take the user to the best valid match, which is usually the primary
 * domain. In the case of nodes, try to find another match.
 *
 * @return
 *   No return. This function issues a drupal_goto();
 */
function domain_invalid_domain_requested() {
  global $_domain, $user;

  // Some users are allowed to view inactive domains.
  if (user_access('access inactive domains')) {
    return;
  }
  $path = drupal_get_normal_path($_GET['q']);
  $allow = array_filter(module_invoke_all('domain_invalid_request', $path, $_domain, $user));
  if (!empty($allow)) {
    return;
  }

  // Check to see if this is a node page. These are redirected to a visible page, if possible.
  $node = menu_get_object();
  if (empty($node->nid)) {
    $item = menu_get_item();
    $path = $item['href'];
    if (drupal_is_front_page($item['href'])) {
      $path = '';
    }
    $default = domain_default();

    // Log the access attempt.
    watchdog('domain', 'Invalid domain requested by %user on %domain; redirected to %default.', array(
      '%user' => isset($user->name) ? $user->name : variable_get('anonymous', t('Anonymous')),
      '%domain' => $_domain['sitename'],
      '%default' => $default['sitename'],
    ), WATCHDOG_WARNING);
    drupal_goto($default['path'] . drupal_get_path_alias($path));
  }

  // Try to find the proper redirect for a node.
  $path = "node/{$node->nid}";
  $domain = domain_get_node_match($node->nid);
  if ($domain['valid']) {
    $redirect = $domain;
  }
  elseif (!empty($node->domains)) {
    foreach ($node->domains as $domain_id) {
      $domain = domain_lookup($domain_id);
      if ($domain['valid']) {
        $redirect = $domain;
        break;
      }
    }
  }

  // If we found no node matches, just go to the home page.
  $extra = ' ' . t('node page.');
  if (empty($redirect)) {
    $redirect = domain_default();
    $path = '';
    $extra = '.';
  }

  // Log the access attempt.
  watchdog('domain', 'Invalid domain requested by %user on %domain, redirected to %redirect', array(
    '%user' => isset($user->name) ? $user->name : variable_get('anonymous', t('Anonymous')),
    '%domain' => $_domain['sitename'],
    '%redirect' => $redirect['sitename'] . $extra,
  ), WATCHDOG_WARNING);
  drupal_goto($redirect['path'] . drupal_get_path_alias($path));
}

/**
 * Implements hook_domain_invalid_request().
 */
function domain_domain_invalid_request($path, $_domain, $account) {
  if ($path == 'user/login') {
    return TRUE;
  }
  return FALSE;
}

/**
 * On a node save, make sure the editor is returned to a domain that can view
 * the node.
 *
 * The node id is saved in the $_SESSION during hook_nodeapi().
 * We must do this because node_form_submit() overrides the
 * form's redirect values.
 *
 * For extra checking, we also store the source domain_id and
 * try to redirect to that domain if we accidentally moved. However,
 * the node must be visible on that domain.
 *
 * @return
 *   No return value. Issue a drupal_goto() if needed.
 */
function domain_node_save_redirect() {
  global $_domain;

  // If no session token, nothing to do.
  if (!isset($_SESSION['domain_save_id'])) {
    return;
  }
  $domain_id = $_SESSION['domain_save_id'];

  // Unset the token now so as not to repeat this step.
  unset($_SESSION['domain_save_id']);
  $source = domain_lookup($domain_id);
  if ($source['domain_id'] != -1 && $source['domain_id'] != $_domain['domain_id'] && ($source['valid'] || user_access('access inactive domains'))) {
    domain_goto($source);
  }
}

/**
 * Determines the domain matching the given hostname.
 *
 * This function runs a lookup against the {domain} table matching the
 * subdomain column against the given parameter $_name. If a match is
 * found the function returns an array containing the domain requested
 * and the matching domain_id from the {domain} table.
 *
 * If no match is found domain_id is set to 0 for the default domain.
 *
 * During the process hook_domain_bootstrap_lookup() is invoked to allow other
 * modules to modify that result.
 *
 * @param $name
 *   The string representation of a {domain} entry.
 *
 * @param $reset
 *   Set TRUE to ignore cached versions and look the name up again. Optional.
 *
 * @return
 *   An array containing a domain_id from {domain} matching the given domain name
 */
function domain_lookup_simple($name, $reset = FALSE) {
  $cache =& drupal_static(__FUNCTION__, array());
  if (empty($name)) {
    return array();
  }
  if ($reset || !isset($cache[$name])) {

    // Lookup the given domain name against our allowed hosts record.
    $domain = db_query("SELECT domain_id, subdomain, sitename FROM {domain} WHERE subdomain = :subdomain", array(
      ':subdomain' => $name,
    ))
      ->fetchAssoc();
    if (!is_array($domain)) {
      $domain = array();
    }

    // If no match => use default domain.
    if (!isset($domain['domain_id'])) {
      $domain['domain_id'] = domain_default_id();
    }
    $domain['subdomain'] = $name;

    // Invoke hook_domain_bootstrap_lookup()
    $domain_new = _domain_bootstrap_invoke_all('lookup', $domain);
    if (is_array($domain_new)) {
      if (isset($domain_new['domain_id']) && is_array($domain_new['domain_id'])) {
        foreach ($domain_new as $key => $value) {
          if (is_array($value)) {
            $domain_new[$key] = $value[0];
          }
        }
        $modules = array();
        foreach (_domain_bootstrap_modules() as $module) {
          if (function_exists($module . '_domain_bootstrap_lookup')) {
            $modules[] = $module;
          }
        }
        $lookup = domain_lookup($domain_new['domain_id']);
        $domain_new['error'] = t('domain lookup. More than one registered domain was returned. Defaulting to %domain. The likely cause is a conflict between %modules', array(
          '%domain' => $lookup['sitename'],
          '%modules' => implode(', ', $modules),
        ));
      }
      $domain = array_merge($domain, $domain_new);
    }
    $cache[$name] = $domain;
  }
  return $cache[$name];
}

/**
 * Implements hook_domain_install().
 */
function domain_domain_install() {

  // Set the proper variables for our bootstrap load, as a precaution.
  domain_bootstrap_register();
}

/**
 * Implements hook_domain_batch().
 */
function domain_domain_batch() {

  // Change all the domain names at once.
  $batch = array();
  $batch['subdomain'] = array(
    '#form' => array(
      '#title' => t('Domains'),
      '#type' => 'textfield',
      '#size' => 40,
      '#maxlength' => 80,
      '#description' => t('Enter the host value of the domain.  No http:// or slashes.'),
      '#required' => TRUE,
    ),
    '#domain_action' => 'domain',
    '#meta_description' => t('Edit all domain values.'),
    '#data_type' => 'string',
    '#update_all' => FALSE,
    '#weight' => -10,
  );

  //Change all the site names at once.
  $batch['sitename'] = array(
    '#form' => array(
      '#title' => t('Names'),
      '#type' => 'textfield',
      '#size' => 40,
      '#maxlength' => 80,
      '#description' => t('The human-readable name for this domain.'),
      '#required' => TRUE,
    ),
    '#domain_action' => 'domain',
    '#meta_description' => t('Edit all domain names.'),
    '#data_type' => 'string',
    '#update_all' => FALSE,
    '#weight' => -10,
  );

  // Change all the schemes at once.
  $batch['scheme'] = array(
    '#form' => array(
      '#title' => t('URL schemes'),
      '#type' => 'radios',
      '#options' => array(
        'http' => 'http://',
        'https' => 'https://',
      ),
      '#description' => t('The URL scheme for accessing this domain.'),
    ),
    '#domain_action' => 'domain',
    '#meta_description' => t('Edit all domain URL schemes.'),
    '#system_default' => variable_get('domain_scheme', 'http://'),
    '#data_type' => 'string',
    '#update_all' => TRUE,
    '#weight' => -10,
  );

  // Change all the valid flags at once.
  $batch['valid'] = array(
    '#form' => array(
      '#title' => t('Valid domains'),
      '#type' => 'radios',
      '#options' => array(
        1 => t('Active'),
        0 => t('Inactive'),
      ),
      '#description' => t('Allows users to access this domain.'),
    ),
    '#domain_action' => 'domain',
    '#meta_description' => t('Edit all domain status flags.'),
    '#system_default' => 1,
    '#data_type' => 'integer',
    '#update_all' => TRUE,
    '#weight' => -10,
  );
  foreach ($batch as $key => $value) {
    $batch[$key]['#module'] = t('Domain Access');
  }
  return $batch;
}

/**
 * Sets a message to the site admin.
 *
 * If our module changes $conf settings, they may be reflected
 * on admin pages when we don't want them to be.
 *
 * @param $form_id
 *  The form_id of the active form.
 *
 * @return
 *  No return value. Set the proper message to the user.
 */
function domain_warning_check($form_id) {
  $_warning =& drupal_static(__FUNCTION__);

  // If $_POST is set, we are submitting the form and should not set a message.
  if (empty($_POST) && empty($_warning)) {
    global $_domain;

    // Add the list of forms
    $forms = array();
    $forms = module_invoke_all('domain_warning');
    drupal_alter('domain_warnings', $forms);
    if ($form_id == 'domain_batch_form' || arg(2) != 'domain' && in_array($form_id, array_keys($forms))) {
      $default = domain_default();
      $link_text = '';
      $link = isset($forms[$form_id]) ? $forms[$form_id] : NULL;
      if (!empty($link)) {
        $elements = array();
        foreach ($_domain as $key => $value) {
          if (!is_array($value)) {
            $elements[$key] = $value;
          }
        }
        $replace = explode('|', '%' . implode('|%', array_keys($elements)));
        $values = explode('|', implode('|', $elements));
        $link = str_replace($replace, $values, $link);
        $link_text = t('You may submit changes to the current domain at <a href="!url">%link</a>.', array(
          '!url' => url($link),
          '%link' => $link,
        ));
      }
      $_path = domain_get_uri($default);
      $message = '';
      if ($_domain['domain_id'] != $default['domain_id']) {
        $message = ' ' . t('and <a href="!url">may need to be entered from !domain</a>', array(
          '!url' => $_path,
          '!domain' => $default['subdomain'],
        ));
      }
      else {
        $link_text = '';
      }
      drupal_set_message(t('This form submits changes to your default
      configuration!message. !link', array(
        '!message' => $message,
        '!link' => $link_text,
      )), 'warning', FALSE);
    }
    $_warning = TRUE;
  }
}

/**
 * Helper function for passing hook_domainpath() by reference.
 *
 * @param $domain_id
 *   The domain_id taken from {domain}.
 * @param $path
 *   The internal drupal path to the node.
 * @param $path_language
 *   Optional language code to look up the path in.
 *
 * @return
 *   The $path, modified by reference by hook_domainpath() implementations.
 */
function domain_path($domain_id, &$path, &$options, $original_path) {
  $modules = _domain_path_modules();
  if (!empty($modules)) {
    foreach ($modules as $module) {

      // Cannot use module_invoke_all() since these are passed by reference.
      $function = $module . '_domain_path';
      $function($domain_id, $path, $options, $original_path);
    }
  }
}

/**
 * Helper function for domain_path() checks.
 */
function _domain_path_modules() {
  $modules =& drupal_static(__FUNCTION__);
  if (!isset($modules)) {
    $modules = module_implements('domain_path');
  }
  return $modules;
}

/**
 * Implements hook_domain_ignore().
 */
function domain_domain_ignore() {

  // We cannot interfere with update processes.
  return array(
    'update_script_selection_form',
    'authorize_filetransfer_form',
  );
}

/**
 * Implements hook_node_access_explain().
 */
function domain_node_access_explain($row) {
  $_domain = domain_get_domain();
  $active = $_domain['subdomain'];
  $domain = domain_lookup($row->gid);
  $return = t('Domain Access') . ' -- ';
  switch ($row->realm) {
    case 'domain_all':
      if (domain_grant_all() == TRUE) {
        $return .= t('True: Allows content from all domains to be shown.');
      }
      else {
        if (!empty($_domain['site_grant'])) {
          $return .= t('False: Only allows content from the active domain (%domain) or from all affiliates.', array(
            '%domain' => $active,
          ));
        }
        else {
          $return .= t('False: Only allows content from the active domain (%domain).', array(
            '%domain' => $active,
          ));
        }
      }
      break;
    case 'domain_site':
      $return .= t('Viewable on all affiliate sites.');
      break;
    case 'domain_id':
      $return .= t('Viewable on %domain<br />%domain privileged editors may edit and delete', array(
        '%domain' => $domain['subdomain'],
      ));
      break;
    case 'domain_unpublished':
      $return .= t('Unpublished node. Viewable on %domain<br />%domain privileged editors may edit and delete', array(
        '%domain' => $domain['subdomain'],
      ));
      break;
    default:

      // This is not our grant, do not return anything.
      $return = NULL;
      break;
  }
  return $return;
}

/**
 * Implements hook_node_access_acknowledge().
 */
function domain_node_access_acknowledge($grant) {
  if ($grant['realm'] == 'domain_all') {
    return TRUE;
  }
}

/**
 * Implements hook_field_extra_fields()
 *
 * Field hook to allow sorting of the domain settings field.
 */
function domain_field_extra_fields() {
  $base = array(
    'domain' => array(
      'label' => t('Domain access'),
      'description' => t('Domain Access settings.'),
      'weight' => 1,
    ),
  );
  $base_display = $base;
  $base_display['domain']['label'] .= ' ' . t('(debugging only)');

  // Node settings.
  $extra = array();
  foreach (node_type_get_names() as $name => $value) {
    $extra['node'][$name]['form'] = $base;
    $extra['node'][$name]['display'] = $base_display;
  }

  // User settings.
  $extra['user']['user']['form'] = $base;
  $extra['user']['user']['display'] = $base_display;
  return $extra;
}

/**
 * Simple function to clean strings for use in for example paths.
 */
function domain_url_encode($string) {
  $string = drupal_strtolower($string);

  // Remove slashes.
  $string = str_replace('/', '', $string);

  // Reduce to the subset of ASCII96 letters and numbers - from Pathauto.
  $pattern = '/[^a-zA-Z0-9\\/]+/ ';
  $string = preg_replace($pattern, '', $string);

  // Remove white space - from Pathauto.
  $string = preg_replace('/\\s+/', '', $string);
  return $string;
}

/**
 * Implements hook_simpletest()
 */
function domain_simpletest() {
  $module_name = 'domain';
  $dir = drupal_get_path('module', $module_name) . '/tests';
  $tests = file_scan_directory($dir, '/\\.test$/');
  return array_keys($tests);
}

/**
 * Implements hook_query_TAG_alter().
 *
 * If enabled, force admins to use Domain Access rules.
 */
function domain_query_node_access_alter(QueryAlterableInterface $query) {
  if (domain_admin_filter()) {
    domain_alter_node_query($query, 'node');
  }
}

/**
 * Determines if node lists should be filtered for admins.
 *
 * @return
 *   Boolean TRUE or FALSE.
 */
function domain_admin_filter() {
  static $return;
  if (!isset($return)) {
    $admin_force = variable_get('domain_force_admin', FALSE);

    // In any of the following cases, do not enforce any rules.
    if (empty($admin_force) || !user_access('bypass node access') || domain_grant_all()) {
      $return = FALSE;
    }
    else {
      $return = TRUE;
    }
  }
  return $return;
}

/**
 * Abstraction to allow query alters outside of node access.
 *
 * This entire function is stolen from node.module. We should fix this in core.
 *
 * @link http://drupal.org/node/1363062
 *
 * @param $query
 *   A dynamic node query.
 * @param $type
 *   Either 'node' or 'entity' depending on what sort of query it is. See
 *   node_query_node_access_alter() and node_query_entity_field_access_alter()
 *   for more. Currently, we only support 'node'.
 */
function domain_alter_node_query(QueryAlterableInterface $query, $type) {
  global $user;

  // Read meta-data from query, if provided.
  if (!($account = $query
    ->getMetaData('account'))) {
    $account = $user;
  }
  if (!($op = $query
    ->getMetaData('op'))) {
    $op = 'view';
  }

  // Only act on view.
  if ($op != 'view') {
    return;
  }
  $tables = $query
    ->getTables();
  $base_table = $query
    ->getMetaData('base_table');

  // If no base table is specified explicitly, search for one.
  if (!$base_table) {
    $fallback = '';
    foreach ($tables as $alias => $table_info) {
      if (!$table_info instanceof SelectQueryInterface) {
        $table = $table_info['table'];

        // If the node table is in the query, it wins immediately.
        if ($table == 'node') {
          $base_table = $table;
          break;
        }

        // Check whether the table has a foreign key to node.nid. If it does,
        // do not run this check again as we found a base table and only node
        // can triumph that.
        if (!$base_table) {

          // The schema is cached.
          $schema = drupal_get_schema($table);
          if (isset($schema['fields']['nid'])) {
            if (isset($schema['foreign keys'])) {
              foreach ($schema['foreign keys'] as $relation) {
                if ($relation['table'] === 'node' && $relation['columns'] === array(
                  'nid' => 'nid',
                )) {
                  $base_table = $table;
                }
              }
            }
            else {

              // At least it's a nid. A table with a field called nid is very
              // very likely to be a node.nid in a node access query.
              $fallback = $table;
            }
          }
        }
      }
    }

    // If there is nothing else, use the fallback.
    if (!$base_table) {
      if ($fallback) {
        watchdog('security', 'Your node listing query is using @fallback as a base table in a query tagged for node access. This might not be secure and might not even work. Specify foreign keys in your schema to node.nid ', array(
          '@fallback' => $fallback,
        ), WATCHDOG_WARNING);
        $base_table = $fallback;
      }
      else {
        throw new Exception(t('Query tagged for node access but there is no nid. Add foreign keys to node.nid in schema to fix.'));
      }
    }
  }

  // Find all instances of the base table being joined -- could appear
  // more than once in the query, and could be aliased. Join each one to
  // the node_access table.
  $grants = node_access_grants($op, $account);
  if ($type == 'entity') {

    // The original query looked something like:
    // @code
    //  SELECT nid FROM sometable s
    //  INNER JOIN node_access na ON na.nid = s.nid
    //  WHERE ($node_access_conditions)
    // @endcode
    //
    // Our query will look like:
    // @code
    //  SELECT entity_type, entity_id
    //  FROM field_data_something s
    //  LEFT JOIN node_access na ON s.entity_id = na.nid
    //  WHERE (entity_type = 'node' AND $node_access_conditions) OR (entity_type <> 'node')
    // @endcode
    //
    // So instead of directly adding to the query object, we need to collect
    // all of the node access conditions in a separate db_and() object and
    // then add it to the query at the end.
    $node_conditions = db_and();
  }
  foreach ($tables as $nalias => $tableinfo) {
    $table = $tableinfo['table'];
    if (!$table instanceof SelectQueryInterface && $table == $base_table) {

      // Set the subquery.
      $subquery = db_select('node_access', 'na')
        ->fields('na', array(
        'nid',
      ));
      $grant_conditions = db_or();

      // If any grant exists for the specified user, then user has access
      // to the node for the specified operation.
      foreach ($grants as $realm => $gids) {
        foreach ($gids as $gid) {
          $grant_conditions
            ->condition(db_and()
            ->condition('na.gid', $gid)
            ->condition('na.realm', $realm));
        }
      }

      // Attach conditions to the subquery for nodes.
      if (count($grant_conditions
        ->conditions())) {
        $subquery
          ->condition($grant_conditions);
      }
      $subquery
        ->condition('na.grant_' . $op, 1, '>=');
      $field = 'nid';

      // Now handle entities.
      if ($type == 'entity') {

        // Set a common alias for entities.
        $base_alias = $nalias;
        $field = 'entity_id';
      }
      $subquery
        ->where("{$nalias}.{$field} = na.nid");

      // For an entity query, attach the subquery to entity conditions.
      if ($type == 'entity') {
        $node_conditions
          ->exists($subquery);
      }
      else {
        $query
          ->exists($subquery);
      }
    }
  }
  if ($type == 'entity' && count($subquery
    ->conditions())) {

    // All the node access conditions are only for field values belonging to
    // nodes.
    $node_conditions
      ->condition("{$base_alias}.entity_type", 'node');
    $or = db_or();
    $or
      ->condition($node_conditions);

    // If the field value belongs to a non-node entity type then this function
    // does not do anything with it.
    $or
      ->condition("{$base_alias}.entity_type", 'node', '<>');

    // Add the compiled set of rules to the query.
    $query
      ->condition($or);
  }
}

/**
 * Checks to see if the webserver returns a valid response
 * for a request to a domain.
 *
 * If the domain_skip_domain_check variable is not 0, the response will
 * be assumed to be valid and a warning will be set using drupal_set_message().
 *
 * @param $domain
 *   An array containing the record from the {domain} table.
 * @param $drush
 *   Boolean value indicating a drush command.
 *
 * @return
 *   A translated string indicating the error message or FALSE if the response
 *   is 200 "found" or if the domain_skip_domain_check variable is not 0.
 */
function domain_check_response($domain, $drush = FALSE) {

  // t() function name is different whether we are using drush or not.
  $t = empty($drush) ? 't' : 'dt';
  if (variable_get('domain_skip_domain_check', 0)) {
    drupal_set_message($t('Domain response checks have been disabled. Saving an incorrect domain may affect your site.'), 'warning', FALSE);
    return FALSE;
  }
  $url = domain_get_path($domain) . drupal_get_path('module', 'domain') . '/tests/200.png';
  $response = drupal_http_request($url, array(
    'method' => 'HEAD',
    'absolute' => TRUE,
  ));
  if ($response->code != 200) {
    return $t('!server is not responding as expected and may not be configured correctly at the server level. Server code !code was returned.', array(
      '!server' => $url,
      '!code' => $response->code,
    ));
  }
  return FALSE;
}

/**
 * Reassign domain data to a new domain.
 *
 * @param $old_domain
 *   The curent domain record, most commonly passed during a domain deletion.
 * @param $new_domain
 *   The target domain record.
 * @param $table
 *   The database table being affected. This value indicates the type of update
 *   being performed. Core module values are 'domain_access' (indicating that
 *   node records are being affected, and 'domain_editor' (indicating user
 *   records).
 */
function domain_reassign($old_domain, $new_domain, $table) {

  // Re-assign content and users, if specified.
  $old_id = $old_domain['domain_id'];
  $new_id = $new_domain['domain_id'];
  if ($table == 'domain_access') {
    $nids = db_query("SELECT nid FROM {domain_access} WHERE realm = 'domain_id' AND gid = :domain_id", array(
      ':domain_id' => $new_id,
    ))
      ->fetchAllAssoc('nid');
    $query = db_update('domain_access')
      ->condition('gid', $old_id)
      ->condition('realm', 'domain_id')
      ->fields(array(
      'gid' => $new_id,
    ));

    // We cannot update using a subquery, so be sure to exclude duplicates.
    if (!empty($nids)) {
      $query
        ->condition('nid', array_keys($nids), 'NOT IN');
    }
    $query
      ->execute();
  }
  elseif ($table == 'domain_editor') {
    $uids = db_query("SELECT uid FROM {domain_editor} WHERE domain_id = :domain_id", array(
      ':domain_id' => $new_id,
    ))
      ->fetchAllAssoc('uid');
    $query = db_update('domain_editor')
      ->condition('domain_id', $old_id)
      ->fields(array(
      'domain_id' => $new_id,
    ));

    // We cannot update using a subquery, so be sure to exclude duplicates.
    if (!empty($uids)) {
      $query
        ->condition('uid', array_keys($uids), 'NOT IN');
    }
    $query
      ->execute();
  }

  // Let other modules act.
  module_invoke_all('domain_reassign', $old_domain, $new_domain, $table);

  // In all cases, we need to force a menu rebuild, which also clears the cache.
  menu_rebuild();

  // Notify that node access needs to be rebuilt.
  node_access_needs_rebuild(TRUE);
}

/**
 * Implements hook_features_api().
 */
function domain_features_api() {
  $components = array(
    'domain' => array(
      'name' => t('Domains'),
      'default_hook' => 'domain_default_domains',
      'default_file' => FEATURES_DEFAULTS_CUSTOM,
      'default_filename' => 'domains',
      'feature_source' => TRUE,
      'file' => drupal_get_path('module', 'domain') . '/domain.features.inc',
    ),
  );
  return $components;
}

/**
 * Loads a domain from its machine name.
 *
 * @param $machine_name
 *  The machine name of the domain record.
 * @param $full
 *  Boolean indicator to run hook_domain_load() or not.
 *
 * @return
 *  A Domain array or -1 on failure.
 */
function domain_machine_name_load($machine_name, $full = FALSE) {
  $domain = db_query('SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default, machine_name FROM {domain} WHERE machine_name = :machine_name', array(
    ':machine_name' => $machine_name,
  ))
    ->fetchAssoc();
  if ($full && !empty($domain)) {
    $domain = domain_api($domain, TRUE);
  }
  return $domain;
}

/**
 * Create a machine name for a domain record.
 *
 * @param $subdomain
 *  The subdomain string of the record, which should be unique.
 *
 * @return
 *  A string with dot and colon transformed to underscore.
 */
function domain_machine_name($subdomain) {
  return preg_replace('/[^a-z0-9_]+/', '_', $subdomain);
}

/**
 * Checks for unique value of the machine name.
 *
 * @param $machine_name
 *  The machine name (subdomain) of the domain record.
 *
 * @return
 *  Boolean TRUE or FALSE.
 */
function domain_check_machine_name($machine_name) {
  return (bool) db_query("SELECT COUNT(1) FROM {domain} WHERE machine_name = :machine_name", array(
    ':machine_name' => $machine_name,
  ))
    ->fetchField();
}

/**
 * Loads a domain_id from its machine name.
 *
 * @param $machine_name
 *  The machine name (subdomain) of the domain record.
 *
 * @return
 *  A Domain id key.
 */
function domain_load_domain_id($machine_name) {
  $domains = domain_list_by_machine_name();
  if (isset($domains[$machine_name])) {
    return $domains[$machine_name]['domain_id'];
  }
  return FALSE;
}

/**
 * Returns a map of domains by machine name.
 *
 * @return
 *  An array keyed by domain machine_name whose values are domain records.
 */
function domain_list_by_machine_name() {
  $domains =& drupal_static(__FUNCTION__);
  if (!isset($domains)) {
    $list = domain_domains();
    foreach ($list as $record) {
      $domains[$record['machine_name']] = $record;
    }
  }
  return $domains;
}

/**
 * Loads an array of all active machine names.
 */
function domain_machine_names() {
  $list =& drupal_static(__FUNCTION__);
  if (isset($list)) {
    return $list;
  }
  $machine_names = db_query("SELECT machine_name FROM {domain_export}")
    ->fetchAll();
  foreach ($machine_names as $machine_name) {
    $list[] = $machine_name->machine_name;
  }
  return $list;
}

/**
 * Loads a machine name from its domain id.
 *
 * @param $domain_id
 *  The id of the domain record.
 *
 * @return
 *  A Domain machine name.
 */
function domain_load_machine_name($domain_id) {
  return db_query('SELECT machine_name FROM {domain_export} WHERE domain_id = :domain_id', array(
    ':domain_id' => $domain_id,
  ))
    ->fetchField();
}

/**
 * Features doesn't know how to load custom includes.
 *
 * @param $module
 *  The name of the feature to load.
 * @param $hook
 *  The name of the domain hook.
 * @param $return
 *  Boolean indicator to return the results of the function.
 *
 * @return
 *  The results of the $hook implemenation, if requested.
 */
function domain_features_load($module, $hook, $return = TRUE) {

  // Features does not handle module loading of custom files.
  module_load_include('inc', $module, $module . '.domains');
  $function = $module . '_' . $hook;
  if ($return && function_exists($function)) {
    return $function();
  }
}

/**
 * Sets domain export options consistently.
 *
 * @return
 *  An array of form options. Not that we cannot use _ in keys.
 */
function domain_features_get_options() {
  $options = array(
    'wipe-domain-tables' => t('Wipe tables on revert/rebuild'),
    'all-domains' => t('Export all domains'),
  );
  foreach (domain_domains() as $domain) {
    $options[$domain['machine_name']] = $domain['subdomain'];
  }
  return $options;
}

/**
 * Processes export data selections consistently.
 *
 * @param $data
 *  Array of selections from the features component form.
 *
 * @return
 *  An array of domains, keyed by machine_name.
 */
function domain_features_selection($data) {
  $list = array();
  if (!empty($data['all-domains'])) {
    $data = array();
    $domains = domain_domains(TRUE);
    foreach ($domains as $domain) {
      $data[] = $domain['machine_name'];
    }
  }
  foreach ($data as $machine_name) {
    $record = domain_machine_name_load($machine_name);
    if (!empty($record)) {
      $list[$record['machine_name']] = $record['machine_name'];
    }
  }
  return $list;
}

/**
 * Tells Features revert whether to perform a wipe/rebuild.
 *
 * @param &$defaults
 *  The default values from the feature, passed by reference.
 *
 * @return boolean
 */
function domain_features_wipe_tables(&$defaults) {
  if (!empty($defaults['wipe-domain-tables'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Tells Features export how to process a wipe/rebuild statement to code.
 *
 * @param &$data
 *  The data for the feature, passed by reference.
 * @param $code
 *  The code array to export, passed by reference.
 * @param $export
 *  Boolean indicator that we are generator (exporting) or testing code.
 * @param $type
 *  A string indicating the type of domain data to export.
 *
 * @return boolean
 */
function domain_features_export_wipe_tables_code(&$data, &$code, $export, $type) {
  if (!empty($export) && !empty($data['wipe-domain-tables']) || in_array('wipe-domain-tables', $data)) {
    $code[] = "  \${$type}['wipe-domain-tables'] = 'wipe-domain-tables';";
    return TRUE;
  }
  return FALSE;
}

/**
 * Tells Features export how to set a wipe/rebuild statement in the $data.
 *
 * @param &$export
 *  The feature component $export array, passed by reference.
 * @param $data
 *  The data for the feature.
 * @param $type
 *  A string indicating the type of domain data to export.
 */
function domain_features_export_set_wipe_tables(&$export, $data, $type) {
  if (in_array('wipe-domain-tables', $data)) {
    $export['features'][$type]['wipe-domain-tables'] = 'wipe-domain-tables';
  }
}

/**
 * Checks to see if any dependent modules have a domain_id 0 record left.
 *
 * @return
 *  An array of matching modules, with the human readable name and an array of
 *  tables that require an update as values for each entry, keyed by module
 *  name.
 */
function domain_update_module_check() {
  $list = array();
  $modules = system_rebuild_module_data();
  $dependencies = $modules['domain']->required_by;
  $enabled_dependencies = array_intersect_key($dependencies, module_list());
  if (empty($enabled_dependencies)) {
    return $list;
  }
  foreach ($enabled_dependencies as $module => $data) {
    module_load_install($module);
    if ($schema = module_invoke($module, 'schema')) {
      if ($tables = domain_test_schema($schema)) {
        $list[$module]['name'] = $modules[$module]->info['name'];
        $list[$module]['tables'] = $tables;
      }
    }
  }
  return $list;
}

/**
 * Checks a dependent module's tables for a domain_id of 0.
 *
 * @param $schema
 *  An array of table schemas, using the SchemaAPI.
 *
 * @return $tables
 *  An array of matching tables.
 */
function domain_test_schema($schema = array()) {
  $tables = array();
  foreach ($schema as $name => $table) {
    if (db_table_exists($name) && isset($table['fields']['domain_id']['type']) && $table['fields']['domain_id']['type'] == 'int') {
      $query = db_select($name)
        ->condition('domain_id', 0);
      $check = $query
        ->countQuery()
        ->execute()
        ->fetchField();
      if ($check) {
        $tables[] = $name;
      }
    }
  }
  return $tables;
}

/**
 * Creates an array of tables to pass to the update script.
 *
 * @param $list
 *  The modules array returned by domain_update_module_check().
 *
 * @return $tables
 *  An array of tables to modify.
 */
function domain_update_tables($list) {
  $tables = array();
  foreach ($list as $module) {
    $tables = array_merge($tables, $module['tables']);
  }
  return $tables;
}

/**
 * Turns a domain_id of 0 into the default domain.
 *
 * @param $tables
 *  An array of database tables to update.
 *
 * @return
 *  TRUE on success or FALSE on failure.
 */
function domain_update_zero_records($tables) {
  $default_id = domain_default_id();
  $success = TRUE;
  foreach ($tables as $table) {
    $transaction = db_transaction();
    try {
      db_update($table)
        ->fields(array(
        'domain_id' => $default_id,
      ))
        ->condition('domain_id', 0)
        ->execute();
    } catch (Exception $e) {
      $transaction
        ->rollback();
      watchdog_exception('domain_repair', $e);
      drupal_set_message(t('The update failed due to a duplicate record. You must manually correct the {@table} table in your database.', array(
        '@table' => $table,
      )), 'error');
      $success = FALSE;
    }
  }
  return $success;
}

/**
 * Generates messages about required table updates for 7.x.3.
 *
 * This function may be called during module installation, so we use $t to
 * ensure that we use the correct translation function.
 *
 * @see domain_requirements()
 *
 * @param &$messages
 *  An array of translated status messages, passed by reference.
 * @param $list
 *  An array of modules that require updates.
 */
function domain_update_messages(&$messages, $list) {
  $t = get_t();
  foreach ($list as $module => $data) {
    $updated = $t('updated by an administrator');
    if (user_access('administer domains')) {
      $updated = l($t('updated for compatibility'), 'admin/structure/domain/repair');
    }
    $messages[] = $t('@module has a domain_id of 0 and needs to be !updated.', array(
      '@module' => $data['name'],
      '!updated' => $updated,
    ));
  }
}

/**
 * Checks for tables not compatible with 7.x.3 and provides standard messages.
 */
function domain_check_for_update() {
  $list = domain_update_module_check();
  if (!empty($list)) {
    $messages = array();
    domain_update_messages($messages, $list);
    if (!drupal_is_cli()) {
      drupal_set_message(theme('item_list', array(
        'items' => $messages,
      )), 'error', FALSE);
    }
    elseif (function_exists('drush_print')) {
      foreach ($messages as $message) {
        drush_print(" * {$message}");
      }
      drush_print(dt(" ** Run 'drush domain-repair' to update these tables."));
    }
  }
}

/**
 * Defines batch operations for domain settings.
 *
 * @return
 *  An array of elements defined by hook_domain_batch().
 */
function domain_batch_actions() {
  $batch =& drupal_static(__FUNCTION__);
  if (!empty($batch)) {
    return $batch;
  }

  // Get all implementations.
  $batch = module_invoke_all('domain_batch');

  // Allow modules to alter the list.
  drupal_alter('domain_batch', $batch);
  return $batch;
}

/**
 * Implements template_preprocess_html().
 */
function domain_preprocess_html(&$variables) {
  $classes = domain_page_classes();
  foreach ($classes as $class) {
    $variables['classes_array'][] = $class;
  }
}

/**
 * Determine the page classes to apply to the current domain.
 *
 * @return
 *  An array of domain-specific HTML-safe class names.
 */
function domain_page_classes() {
  $classes = array();
  $settings = variable_get('domain_classes', 'domain-[current-domain:machine_name]');
  if (!empty($settings)) {
    $vars = check_plain(token_replace($settings));
    $vars = preg_replace('/(\\r\\n|\\n)/', "\r\n", $vars);
    $vars = explode("\r\n", token_replace($vars));
    foreach ($vars as $var) {
      $classes[] = drupal_clean_css_identifier(trim($var));
    }
  }
  return $classes;
}

/**
 * Implements hook_filter_info().
 */
function domain_filter_info() {
  $filters['domain_url'] = array(
    'title' => t('Insert domain-sensitive URLs'),
    'description' => t('Transforms relative URLs in the format <em>[canonical-url:path/to/item]</em> to canonical URLs.'),
    'process callback' => 'domain_url_filter_process',
    'tips callback' => 'domain_url_filter_tips',
    'cache' => FALSE,
    // These cannot be cached.
    'weight' => -10,
  );
  return $filters;
}

/**
 * Implements hook_filter_FILTER_process() magic callback.
 */
function domain_url_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  $pattern = '/\\[canonical-url:(.+?)\\]/';
  $text = preg_replace_callback($pattern, 'domain_url_replace', $text);
  return $text;
}

/**
 * Processes pattern matches for the url filter.
 *
 * @see domain_url_filter_process()
 *
 * @param $matches
 *  An array of matching elements. Item 1 is the path string.
 *
 * @return
 *  A Drupal-processed url string.
 */
function domain_url_replace($matches) {
  return url($matches[1]);
}

/**
 * Implements hook_filter_FILTER_tips() magic callback.
 */
function domain_url_filter_tips($filter, $format, $long) {
  return t('Enter a relative URL in the format <em>[canonical-url:path/to/item]</em> to make the URL point to the canonical path.');
}

/**
 * Implements hook_metatag_page_cache_cid_parts_alter().
 */
function domain_metatag_page_cache_cid_parts_alter(&$cid_parts) {

  // Make Metatag cache domain specific.
  $_domain = domain_get_domain();
  $cid_parts['domain_id'] = $_domain['domain_id'];
}

/**
 * Returns all domain module tokens in a human-readable array.
 *
 * The array key is the token string (without brackets) and the value is the
 * token string : description.
 *
 * @return
 *  An array of token data formatted as explained above.
 */
function domain_get_tokens($prefixes = array()) {
  if (empty($prefixes)) {
    $prefixes = array(
      'current-domain',
      'default-domain',
    );
  }
  module_load_include('inc', 'domain', 'domain.tokens');
  $info = domain_token_info();
  foreach ($prefixes as $prefix) {
    foreach ($info['tokens']['domain'] as $token => $data) {
      $tokens["{$prefix}:{$token}"] = t('!token : !description', array(
        '!token' => "[{$prefix}:{$token}]",
        '!description' => $data['description'],
      ));
    }
  }
  return $tokens;
}

/*
 * Implements hook_migrate_api().
 */
function domain_migrate_api() {
  $api = array(
    'api' => 2,
    'destination handlers' => array(
      'DomainNodeHandler',
      'DomainUserHandler',
    ),
  );
  return $api;
}

/**
 * Returns default node access settings, ready for a node object.
 *
 * Use domain_default_node_access_settings() when you need those settings in
 * forms, or when you look for default access settings in general.
 *
 * First level keys are
 *  - 'domain_site' (publish to all affiliates) with value TRUE or FALSE.
 *  - 'domain_id' contains the assigned affiliates, where both keys and values
 *    are the domain id.
 *
 * Array of assigned domains can be empty, but only if an admin explicitly
 * defined this. Use $fallback_domains to prevent zero assignments.
 * See domain_default() as an example for a possible fallback domain.
 *
 * @see domain_default_node_access_settings()
 * @see domain_default()
 *
 * @param $type
 *  The type of the node (like 'article').
 * @param $fallback_domains (optional)
 *  An array of domains which are used if no default domain is assigned.
 *
 * @return
 *  An array of default settings, formatted as explained above.
 */
function domain_get_node_defaults($type, $fallback_domains = array()) {
  $_domain = domain_get_domain();
  $defaults = array(
    'domain_id' => array(),
  );

  // We use array_flip for compatibility with node_type_form_submit().
  $settings = array_flip(domain_default_node_access_settings($type));
  $defaults['domain_site'] = isset($settings['DOMAIN_ALL']) ? TRUE : FALSE;

  // Assign the currently active domain, if not disabled by user.
  if (isset($settings['DOMAIN_ACTIVE'])) {
    $defaults['domain_id'][$_domain['domain_id']] = $_domain['domain_id'];
  }

  // Assign the default affiliates.
  foreach ($settings as $key => $value) {
    if ($key != 'DOMAIN_ACTIVE' && $key != 'DOMAIN_ALL') {
      if ($domain = domain_machine_name_load($key)) {
        $defaults['domain_id'][$domain['domain_id']] = $domain['domain_id'];
      }
    }
  }

  // Use fallback domains when no default affiliate exists.
  if (empty($defaults['domain_id']) && !empty($fallback_domains)) {
    foreach ($fallback_domains as $domain) {
      $defaults['domain_id'][$domain['domain_id']] = $domain['domain_id'];
    }
  }
  return $defaults;
}

/**
 * Returns default node access settings for new content.
 *
 * Usually, you may use this function in forms, and when you look for default
 * access settings in general.
 * Use domain_get_node_defaults() when you need an array for a node object.
 *
 * @see domain_get_node_defaults()
 *
 * @param $type
 *  The node type (like 'article').
 *
 * @return
 *  An array of default settings.
 */
function domain_default_node_access_settings($type) {

  // We need to know if there are user-defined settings.
  // If so, we get an array, though it could be empty.
  $settings = variable_get('domain_node_' . $type, NULL);

  // Append default behavior, if there are no user-defined settings.
  if (is_null($settings)) {

    // Assign the currently active domain.
    $settings = array(
      0 => 'DOMAIN_ACTIVE',
    );

    // And publish to all domains when rule allows it.
    if (DOMAIN_INSTALL_RULE) {
      $settings[] = 'DOMAIN_ALL';
    }
  }
  elseif (isset($settings['DOMAIN_ALL'])) {
    $old_settings = $settings;

    // Reset settings array and fill it with correct pattern.
    $settings = array();
    foreach ($old_settings as $key => $value) {

      // Machine_name could be of value 0. The second condition covers this special case.
      if ($value !== 0 || $key === 0 && $value === 0) {
        $settings[] = $value;
      }
    }
  }
  return $settings;
}

Related topics

Functions

Namesort descending Description
domain_admin_filter Determines if node lists should be filtered for admins.
domain_alter_node_query Abstraction to allow query alters outside of node access.
domain_api Helper function for passing hook_domain_load() by reference.
domain_api_version Notify other modules of our API version.
domain_batch_actions Defines batch operations for domain settings.
domain_block_info Implements hook_block_info().
domain_block_view Implements hook_block_view().
domain_boot Ensures that our custom_url_rewrite_outbound() is loaded.
domain_bootstrap_register Register the modules needed to load during bootstrap.
domain_check_for_update Checks for tables not compatible with 7.x.3 and provides standard messages.
domain_check_machine_name Checks for unique value of the machine name.
domain_check_primary Check to see if a redirect to the primary domain is needed.
domain_check_response Checks to see if the webserver returns a valid response for a request to a domain.
domain_check_scheme Ensure that the scheme value has not been hacked.
domain_cron Implements hook_cron().
domain_cron_queue_info_alter Implements hook_domain_cron_queue_info_alter().
domain_cron_status Gets the status of cron.
domain_default Assigns the default settings to domain 0, the root domain.
domain_default_id Return the id of the default domain.
domain_default_machine_name Return the machine_name of the default domain.
domain_default_node_access_settings Returns default node access settings for new content.
domain_delete Delete a domain record.
domain_domains Return all active domains (including the default) as an array.
domain_domain_batch Implements hook_domain_batch().
domain_domain_ignore Implements hook_domain_ignore().
domain_domain_install Implements hook_domain_install().
domain_domain_invalid_request Implements hook_domain_invalid_request().
domain_domain_load Implements hook_domain_load().
domain_editor_list_permissions Helper function to generate standard node permission list for a given type.
domain_enable Upon enabling this module, store the default view grant in the {node_access} table. Then it assigns all users to the primary domain.
domain_features_api Implements hook_features_api().
domain_features_export_set_wipe_tables Tells Features export how to set a wipe/rebuild statement in the $data.
domain_features_export_wipe_tables_code Tells Features export how to process a wipe/rebuild statement to code.
domain_features_get_options Sets domain export options consistently.
domain_features_load Features doesn't know how to load custom includes.
domain_features_selection Processes export data selections consistently.
domain_features_wipe_tables Tells Features revert whether to perform a wipe/rebuild.
domain_field_extra_fields Implements hook_field_extra_fields()
domain_filter_info Implements hook_filter_info().
domain_form_alter Implements hook_form_alter().
domain_form_devel_generate_content_form_alter Add settings to devel generate module.
domain_form_node_type_form_alter Implements hook_form_FORM_ID_alter().
domain_form_permission_check Check a user's permissions for displaying the Domain form on nodes.
domain_form_sitename_submit FormsAPI submit handler to track site name changes.
domain_form_system_site_information_settings_alter Update the default domain's sitename.
domain_form_user_admin_account_alter Implements hook_form_alter().
domain_form_user_form_alter Helper function for the two user forms we care about.
domain_form_user_profile_form_alter Implements hook_form_FORM_ID_alter().
domain_form_user_register_form_alter Implements hook_form_FORM_ID_alter().
domain_get_content_urls Get all possible published URLs pointing to a node.
domain_get_domain Return the currently active domain.
domain_get_node_defaults Returns default node access settings, ready for a node object.
domain_get_node_domains Get the domains for multiple matches, mimicking node_load().
domain_get_node_match Get the best matching domain for a node link.
domain_get_path Determine an absolute path for a domain
domain_get_path_match Allow the lookup of path rewrites.
domain_get_tokens Returns all domain module tokens in a human-readable array.
domain_get_uri Determine an absolute path to the current page.
domain_get_user_domains Get the domains a user is assigned to.
domain_goto Determine if we must switch the active domain.
domain_grant_all Activate the hidden grant for searches.
domain_hook_info Implements hook_hook_info().
domain_id_list Return all active domain_ids.
domain_init Implements hook_init().
domain_initial_domain Store the initially loaded domain, for later use.
domain_invalid_domain_requested Redirect a request to an invalid domain.
domain_list_by_machine_name Returns a map of domains by machine name.
domain_load Menu loader function.
domain_load_domain_id Loads a domain_id from its machine name.
domain_load_machine_name Loads a machine name from its domain id.
domain_lookup Runs a lookup against the {domain} table.
domain_lookup_simple Determines the domain matching the given hostname.
domain_machine_name Create a machine name for a domain record.
domain_machine_names Loads an array of all active machine names.
domain_machine_name_load Loads a domain from its machine name.
domain_menu Implements hook_menu().
domain_metatag_page_cache_cid_parts_alter Implements hook_metatag_page_cache_cid_parts_alter().
domain_migrate_api
domain_modules_disabled Implements hook_modules_disabled().
domain_modules_enabled Implements hook_modules_enabled().
domain_node_access Implements hook_node_access().
domain_node_access_acknowledge Implements hook_node_access_acknowledge().
domain_node_access_create Checks if a user can create content of a specific type on the current domain.
domain_node_access_delete Checks if a user can delete content of a specific type based on domain.
domain_node_access_explain Implements hook_node_access_explain().
domain_node_access_records Implements hook_node_access_records().
domain_node_access_update Checks if a user can edit content of a specific type based on domain.
domain_node_access_view Checks if a user can view unpublished nodes assigned to her domain(s).
domain_node_delete Implements hook_node_delete().
domain_node_grants Implements hook_node_grants().
domain_node_insert Implements hook_node_insert().
domain_node_load Implements hook_node_load().
domain_node_presave Implements hook_node_presave().
domain_node_save_redirect On a node save, make sure the editor is returned to a domain that can view the node.
domain_node_type_delete Implements hook_node_type_delete().
domain_node_update Implements hook_node_update().
domain_node_view Implements hook_node_view()
domain_page_classes Determine the page classes to apply to the current domain.
domain_path Helper function for passing hook_domainpath() by reference.
domain_permission Implements hook_permission().
domain_preprocess_html Implements template_preprocess_html().
domain_query_node_access_alter Implements hook_query_TAG_alter().
domain_reassign Reassign domain data to a new domain.
domain_request_name Determines current, fully qualified domain name.
domain_reset_domain Reset the active domain to its initial version.
domain_resolve_host Resolve an HTTP_HOST to a registered domain.
domain_save Save a domain record.
domain_select_format Determine the default format for domain list forms.
domain_set_default_grant Ensure that the 'domain_all' grant is present.
domain_set_domain Set the active domain to something other than the HTTP request.
domain_set_primary_domain Set the primary domain properly, if necessary.
domain_simpletest Implements hook_simpletest()
domain_static_reset Clear static caches used for domain listings.
domain_test_schema Checks a dependent module's tables for a domain_id of 0.
domain_theme Implements hook_theme().
domain_title Set the title of a menu callback for domain edits.
domain_unique_domain Validate the domain against existing domains.
domain_unserialize Unserialize an object stored in {domain_*} tables.
domain_update_messages Generates messages about required table updates for 7.x.3.
domain_update_module_check Checks to see if any dependent modules have a domain_id 0 record left.
domain_update_tables Creates an array of tables to pass to the update script.
domain_update_users FormsAPI to handle the batch update of users.
domain_update_zero_records Turns a domain_id of 0 into the default domain.
domain_url_encode Simple function to clean strings for use in for example paths.
domain_url_filter_process Implements hook_filter_FILTER_process() magic callback.
domain_url_filter_tips Implements hook_filter_FILTER_tips() magic callback.
domain_url_replace Processes pattern matches for the url filter.
domain_user_delete Implements hook_user_delete().
domain_user_insert Implements hook_user_insert().
domain_user_load Implements hook_user_load().
domain_user_operations Implements hook_user_operations().
domain_user_operation_assign Callback for domain_content_node_operations().
domain_user_save Helper function called by both hook_user_insert() and hook_user_update().
domain_user_set Emsures that a user object has been loaded properly.
domain_user_update Implements hook_user_update().
domain_user_view Implements hook_user_view().
domain_validate Validates a domain string.
domain_valid_domain Validate the domain against all correctable errors.
domain_warning_check Sets a message to the site admin.
_domain_path_modules Helper function for domain_path() checks.
_domain_store_grants Store node_access records in the {domain_access} table.
_domain_user_list Helper function to get the names of all domains for a user.

Constants

Namesort descending Description
DOMAIN_ACTIVE
DOMAIN_ALL Defines constants for content settings.
DOMAIN_ASSIGN_USERS Defines whether to assign users to the default domain on install. You may alter this variable before installing the module. See README.txt.
DOMAIN_EXPORT_STATUS_CODE
DOMAIN_EXPORT_STATUS_DATABASE Defines the export statuses for a domain.
DOMAIN_INSTALL_RULE Defines how to handle access permissions when installing the module. You may alter this variable before installing the module. See README.txt.
DOMAIN_LIST_SIZE Sets a default value at which to start paginating Domain lists.
DOMAIN_SITE_GRANT Defines whether to show affiliated content on all domains. You may alter this variable before installing the module. See README.txt.