domain_user = domain_get_user_domains($user); // Properly load custom_url_rewrite_outbound(). include_once('settings_custom_url.inc'); } /** * Implements hook_init(). * * Inititalizes 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. $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()) { static $initial; if (!isset($initial) && !empty($domain)) { $initial = $domain; } return $initial; } /** * Unserialize an object stored in {domain_*} tables. * * PostGRES 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' => -12 ); $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' => -8 ); $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.', 'weight' => -7 ); // 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' => -5 ); // 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' => $value['#weight'] ); } } $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' => -4 ); $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, ); 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'), ), 'edit domain content' => array( 'title' => t('Edit any content on assigned domains'), ), 'delete domain content' => array( 'title' => t('Delete any content on assigned 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'), ), ); return $permissions; } /** * 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_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_cron'] = 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', ); // 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['switcher'] = array( 'info' => t('Domain switcher'), ); $blocks['information'] = array( 'info' => t('Domain access information'), ); return $blocks; } /** * Implements hook_block_view(). */ function domain_block_view($delta = '') { // Dispatch to sub-function. if (empty($delta)) { return; } $function = 'domain_block_view_' . $delta; return $function(); } /** * A nifty little domain-switcher block, useful during debugging. * * @see domain_block_view() */ function domain_block_view_switcher() { $output = ''; $list = array(); $domains = domain_domains(); $msg = FALSE; foreach ($domains as $domain) { if ($domain['valid']) { $title = $domain['sitename']; $allow = TRUE; } else { $title = $domain['sitename'] . ' *'; $allow = FALSE; if (user_access('access inactive domains')) { $msg = TRUE; $allow = TRUE; } } if ($allow) { $list[] = l($title, domain_get_uri($domain), array('absolute' => TRUE)); } } $items = array('items' => $list); $output = theme('item_list', $items); return array( 'subject' => t('Domain switcher'), 'content' => $output, ); } /** * Prints information about the current node. * * @see domain_block_view() */ function domain_block_view_information() { $output = ''; $node = menu_get_object(); if (empty($node->nid)) { return; } // Print the assigned domains. if (!empty($node->subdomains)) { $output .= theme('item_list', array('items' => $node->subdomains, 'title' => t('Assigned domains'))); } // Print the link source domain. $this_domain = domain_get_node_match($node->nid); $output .= theme('item_list', array('items' => array(check_plain($this_domain['sitename'])), 'title' => t('Source domain'))); if (empty($output)) { $output = t('This node is not assigned to a domain.'); } else { $output = '

' . t('%node is published with the following Domain Access rules:', array('%node' => $node->title)) . '

' . $output; } return array( 'subject' => t('Domain access information'), 'content' => $output, ); } /** * 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 nodes' and 'delete domain nodes' permissions are in. */ function domain_user_load($users) { foreach ($users as $uid => $account) { $users[$uid]->domain_user = domain_get_user_domains($account); } } /** * 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) { // 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, TRUE); // By default, the requesting domain is assigned. if (empty($account->domain_user)) { $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_user'] = array( '#type' => 'fieldset', '#title' => t('Domain access'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#weight' => 1 ); $form['domain_user']['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 nodes" permission.'), '#default_value' => $default ); if ($format) { $form['domain_user']['domain_user']['#multiple'] = TRUE; $form['domain_user']['domain_user']['#size'] = count($options) > 10 ? 10 : count($options); } } else { $form['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' => '
' . t('If you select Assign users to domains above, you should confirm the Affiliate editor options settings below.') . '
', '#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. Note: this will erase any current assignment for the selsected users.'), '#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, TRUE); // Behavior 1: add new domains. if ($form_state['values']['behavior'] == 1) { $domains += $current; } // Behavior 2: remove new domains. 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. * * The passed parameter will be checked against the {domain} table for * valid records. * * @param $domain_id * The id request for a specific domain. * * @return * $domain array on success or FALSE on failure. */ function domain_load($domain_id = NULL) { $domain = domain_lookup($domain_id); if ($domain == -1) { return FALSE; } else { 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) { // 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; } // Update or insert a record? $update = (isset($values['domain_id']) && is_numeric($values['domain_id'])) ? array('domain_id') : array(); if (!empty($update)) { $action = 'domain_update'; } else { $action = 'domain_insert'; } drupal_write_record('domain', $values, $update); // 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); return $domain; } /** * Delete a domain record. * * @param $domain * The domain record being deleted. * @param $values * An array of values passed by a form submit, if any. */ function domain_delete($domain, $values = array()) { // Delete the record. db_delete('domain') ->condition('domain_id', $domain['domain_id']) ->execute(); // Let other modules act. module_invoke_all('domain_delete', $domain, $values); // 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. * * @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) { static $domains, $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) { $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; } // Run the lookup, if needed. 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) { static $default; if (empty($default) || $reset) { $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 ); } if ($alter) { // 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() { static $id; if (!isset($id)) { $default = domain_default(); $id = $default['domain_id']; } return $id; } /** * 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)) { db_insert('domain') ->fields(array( 'subdomain' => $root, 'sitename' => $site, 'scheme' => $scheme, 'weight' => -1, 'valid' => 1, 'is_default' => 1, )) ->execute(); // Allow other modules to respond to changes. module_invoke_all('domain_update', domain_default(TRUE)); } } /** * Return all active domains (including the default) as an array. * * @param $reset * A boolean flag indicating whether to reset the static array or not. * * @return * An array of all active domains, with the domain_id as the key. */ function domain_domains($reset = FALSE) { static $domains; if (empty($domains) || $reset) { $domains = array(); // Query the db for active domain records. $result = db_query("SELECT domain_id FROM {domain} ORDER BY weight", array(), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $data) { $domain = domain_lookup($data['domain_id'], NULL, $reset); $domains[$domain['domain_id']] = $domain; } } return $domains; } /** * 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. if (substr_count($subdomain, '.') == 0 && $subdomain != 'localhost') { $error_list[] = t('At least one dot (.) is required, except for localhost.'); } // Check for one colon only. if (substr_count($subdomain, ':') > 1) { $error_list[] = t('Only one colon (:) is allowed.'); } // If a colon, make sure it is only followed by numbers. 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.'); } // 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. * @param $reset * A boolean flag indicating whether to reset the static array or not. * * @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, $reset = FALSE) { static $domains = array(); if (empty($account)) { // This may happen when creating a new user. return array(); } $uid = (int) $account->uid; if (!isset($domains[$uid]) || $reset) { $domains[$uid] = array(); $result = db_query("SELECT domain_id FROM {domain_editor} WHERE uid = :uid", array(':uid' => $uid)); foreach ($result as $data) { $domains[$uid][$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)) { foreach ($filter as $domain_id => $status) { if ($status) { $domains[$uid][$domain_id] = $domain_id; } } } } } } return $domains[$uid]; } /** * Helper function for passing hook_domain_load() by reference. * * @param $domain * The domain array defined by domain_lookup(). * @param $reset * A boolean flag to clear the static variable if necessary. * * @return * The $domain array, modified by reference by hook_domain_load() implementations. */ function domain_api($domain, $reset = FALSE) { static $_modules; if (!isset($_modules) || $reset) { $_modules = module_implements('domain_load'); } if (!empty($_modules)) { foreach ($_modules as $module) { // Cannot use module_invoke_all() since these are passed by reference. $function = $module . '_domain_load'; $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 similuate 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(); if ($_domain['domain_id'] != domain_default_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']; } $_url = parse_url($base_url); // PHP 5 does not return an empty path element. if (!isset($_url['path'])) { $_url['path'] = '/'; } // We need a trailing slash at the end of the path if (substr($_url['path'], -1) != '/') { $_url['path'] .= '/'; } $path = domain_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, $request_uri, $_GET['q']); // Run the result through url() for proper language and path handling. $request_uri = url($request_uri, $options); } // The simple case just uses the inbound path, with the base path present. 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; } // Append the domain grants to the node for editing. $nodes[$node->nid]->domains = $domains[$node->nid]['domain_id']; $nodes[$node->nid]->domain_site = $domains[$node->nid]['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)) { return; } // Build $domains array based on domains checked in the generate form // and shuffle for randomization $checked = array_filter($node->devel_generate['domains']); 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; } } } /** * 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) { static $domain = 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); drupal_alter('domain_source', $source, $nid); $domain[$nid] = $source; } return $source; } /** * Allow the lookup of path rewrites. * * Note that unlinke 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) { global $_domain; $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. * @param $reset * A boolean flag indicating the need to reset the static variable for the * node. * * @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, $reset = FALSE) { static $lookup; if (!isset($lookup) || $reset) { $lookup = array(); } // Ensure we form our data properly. if (!is_array($nodes)) { $node_ids[$nodes] = $nodes; $array = FALSE; } else { $node_ids = $nodes; $array = TRUE; } if (!$array && isset($lookup[$nodes])) { return $lookup[$nodes]; } // Set the proper value for the node. $domains = array('domain_id' => array(), 'domain_site' => FALSE); $result = db_query("SELECT nid, gid, realm FROM {domain_access} WHERE nid IN (:nid) AND (realm = 'domain_id' OR realm = 'domain_site')", array(':nid' => array_keys($node_ids))); foreach ($result as $data) { if ($data->realm == 'domain_id') { $domains['domain_id'][$data->gid] = $data->gid; } elseif ($data->realm == 'domain_site') { $domains['domain_site'] = TRUE; } $lookup[$data->nid] = $domains; } // Return all data or just a single node? if ($array) { return $lookup; } return $lookup[$nodes]; } /** * 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(); // 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. $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 { // The $account may not have domain information loaded, so get it. $domains = domain_get_user_domains($account); $perm = 'delete domain nodes'; if ($op == 'update') { $perm = 'edit domain nodes'; } 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; } } } } } } 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) { $_domain = domain_get_domain(); // 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. $node->domain_site = variable_get('domain_node_' . $node->type, variable_get('domain_behavior', DOMAIN_INSTALL_RULE)); // And the currently active domain. $node->domains = array($_domain['domain_id'] => $_domain['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 publshing' 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, // If this value is > 0, then other grants will not be recorded ); } // Set the domain-specific grants. if (!empty($node->domains)) { $domains = array_filter($node->domains); } if (!empty($domains)) { foreach (array_filter($node->domains) as $key => $value) { $grants[] = array( 'realm' => 'domain_id', 'gid' => $key, 'grant_view' => $node->status, 'grant_update' => 1, 'grant_delete' => 1, 'priority' => 0, ); } } // At least one option must be present, and it is the default site // this prevents null values in the form. // If we are enabling the module for the first time, we set the // default domain of all existing nodes to the root domain. else { $default = domain_default(); $grants[] = array( 'realm' => 'domain_id', 'gid' => $default['domain_id'], 'grant_view' => $node->status, '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) { $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 static lookup for this node. domain_get_node_domains($nid, TRUE); // 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) { static $check = 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(); } } } /** * 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 = variable_get('domain_behavior', 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 = domain_get_domain(); // By default, the requesting domain is assigned. $default = array($_domain['domain_id']); // 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. $all_sites = (int) variable_get('domain_behavior', DOMAIN_INSTALL_RULE); if ($all_sites == 1) { $behavior = 1; } else { $behavior = variable_get('domain_node_' . $form['#node']->type, 0); } // Some options will be passed as hidden values, we need to run some checks on those. if (isset($form['#node']->nid)) { $raw = $form['#node']->domains; } else { $raw = $default; } $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' => FALSE, '#group' => variable_get('domain_vertical_tab', 0) ? 'additional_settings' : '', ); $form['domain']['domain_site'] = array( '#type' => 'checkbox', '#prefix' => t('

Publishing options:'), '#suffix' => '

', '#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 : $behavior, ); $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 : $default, ); if ($format) { $form['domain']['domains']['#multiple'] = TRUE; $form['domain']['domains']['#size'] = count($options) > 10 ? 10 : count($options); } } // If the user has limited permissions, show that form or obey the settings. 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; } } $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; } // This is only used in case 3, below. It means that some options // are present that the user cannot access but that must be preserved. 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)) { $form = array(); return drupal_access_denied(); } $form['domain'] = array( '#type' => 'fieldset', '#title' => t('Affiliate publishing options'), '#collapsible' => TRUE, '#collapsed' => FALSE, ); // 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 : $behavior, ); // Domains that have been assigned and cannot be changed. $form['domains_raw'] = array( '#type' => 'value', '#value' => $raw, ); } } /** * Update the default domain's sitename. */ function domain_form_system_site_information_settings_alter(&$form, &$form_state) { $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) { static $grant; $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); if (variable_get('domain_cron_rule', 1) && $script == 'cron.php') { $options['script'] = $script; $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)/', '/\\\\\*/', '/(^|\|)\\\\($|\|)/'), 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_modules_enabled(). */ function domain_modules_enabled() { domain_bootstrap_register(); } /** * 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() { // Drush may set this value to 'default' if it is empty. if (!empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] != 'default') { // We lower case this, since EXAMPLE.com == example.com. return strtolower(rtrim($_SERVER['HTTP_HOST'])); } else { $domain = domain_default(FALSE, FALSE); return $domain['subdomain']; } } /** * 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; } // 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' => $user->name, '%domain' => $_domain['sitename'], '%redirect' => $redirect['sitename'] . $extra), WATCHDOG_WARNING); drupal_goto($redirect['path'] . drupal_get_path_alias($path)); } /** * 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 acidentally 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 domainname */ function domain_lookup_simple($name, $reset = FALSE) { static $cache = 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)) { $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_delete(). * * @TODO: reassign orphans? */ function domain_domain_delete($domain, $form_values = array()) { if ($domain != -1) { // 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(); } // In all cases, we need to force a menu rebuild, which also clears the cache. menu_rebuild(); } /** * 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 sitenames at once. $batch['sitename'] = array( '#form' => array( '#title' => t('Site names'), '#type' => 'textfield', '#size' => 40, '#maxlength' => 80, '#description' => t('The site name to display for this domain.'), '#required' => TRUE, ), '#domain_action' => 'domain', '#meta_description' => t('Edit all domain site 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. */ function domain_warning_check($form_id) { static $_warning; // 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(); if ($_domain['domain_id'] == $default['domain_id']) { return; } $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 %link.', array('!url' => url($link), '%link' => $link)); } $_path = domain_get_uri($default); drupal_set_message(t('This form submits changes to your primary domain and may need to be entered from !domain. !link', array('#this' => $_domain['subdomain'], '!url' => $_path, '!domain' => $default['subdomain'], '!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() { static $modules; if (!isset($modules)) { $modules = module_implements('domain_path'); } return $modules; } /** * Implements hook_domain_ignore(). */ function domain_domain_ignore() { // In Drupal 6, the update script behaves differently than D5, so we ignore it. return array('update_script_selection_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 { $return .= t('False: Only allows content from the active domain (%domain) or from all affiliates.', array('%domain' => $active)); } break; case 'domain_site': $return .= t('Viewable on all affiliate sites.'); break; case 'domain_id': $return .= t('Viewable on %domain
%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_acknowlegde(). */ 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, ), ); $extra = array(); foreach (node_type_get_names() as $name => $value) { $extra['node'][$name]['form'] = $base; $extra['node'][$name]['display'] = $base; } 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) { $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; } domain_alter_node_query($query); } /** * Abstraction to allow query alters outside of node access. * * @param $query * A dynamic node query. * @param $all_affiliates * Boolean value indicating whether to grant the 'domain_site' grant. */ function domain_alter_node_query(QueryAlterableInterface $query, $all_affiliates = TRUE) { $_domain = domain_get_domain(); $domain_id = (int) $_domain['domain_id']; $tables = $query->getTables(); foreach ($tables as $nalias => $tableinfo) { $table = $tableinfo['table']; if (!($table instanceof SelectQueryInterface) && $table == 'node') { $access_alias = $query->join('domain_access', 'da_admin', "da_admin.nid = {$nalias}.nid"); $or = db_or(); if ($all_affiliates) { $or->condition(db_and() ->condition("{$access_alias}.gid", 0) ->condition("{$access_alias}.realm", 'domain_site') ); } $or->condition(db_and() ->condition("{$access_alias}.gid", $domain_id) ->condition("{$access_alias}.realm", 'domain_id') ); $query->condition($or); // Node module will not have run, so add the distinct. $query->distinct(); } } }