domain_user = domain_get_user_domains($user);
// Properly load custom_url_rewrite_outbound().
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.
$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' => -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,
);
$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_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);
}
}
/**
* 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 content" 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 selected 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 = 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?
$check = domain_check_machine_name($values['machine_name']);
if ($check) {
$action = 'domain_update';
$update = array('machine_name');
$update_id = array('domain_id');
}
else {
$action = 'domain_insert';
$update = array();
$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))
->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);
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);
// In all cases, we need to force a menu rebuild, which also clears the cache.
menu_rebuild();
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. 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.
menu_rebuild();
// 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) {
$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;
}
// 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) {
$default = &drupal_static(__FUNCTION__);
if (empty($default) || $reset) {
// 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 ($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() {
$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;
}
/**
* 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 = 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) {
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)) {
// If this role is assigned to "All domains"
if (!empty($filter['all'])) {
// Add all active domains to the user
foreach (domain_domains() as $domain_id => $domain) {
$domains[$uid][$domain_id] = $domain_id;
}
// We don't need to loop anymore, as all roles have already been added.
break;
}
// Else: add to user domains assigned to this role
else {
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) {
$_modules = &drupal_static(__FUNCTION__);
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 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(TRUE, TRUE);
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;
}
// 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.
// 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'] : variable_get('domain_behavior', DOMAIN_INSTALL_RULE);
$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);
}
/**
* 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;
}
/**
* 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();
// 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 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;
}
}
}
}
}
}
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 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, // 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 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) {
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) {
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 = 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) {
// The node must be assigned to a domain to continue.
if (empty($node->domains)) {
return NODE_ACCESS_IGNORE;
}
// If the node falls outside this user's assigned domains, do nothing.
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 = 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;
}
}
// 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;
}
// 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) {
$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);
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();
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;
}
// 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));
}
/**
* 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('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.
*
* @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 %link.', array('!url' => url($link), '%link' => $link));
}
$_path = domain_get_uri($default);
$message = '';
if ($_domain['domain_id'] != $default['domain_id']) {
$message = t(' and may need to be entered from !domain', array('!url' => $_path, '!domain' => $default['subdomain']));
}
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 {
$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_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.'));
}
}
}
// Prevent duplicate records.
$query->distinct();
// 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
// in a separate db_and() object and then at the end add it to the query.
$entity_conditions = db_and();
}
foreach ($tables as $nalias => $tableinfo) {
$table = $tableinfo['table'];
if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
// The node_access table has the access grants for any given node so JOIN
// it to the table containing the nid which can be either the node
// table or a field value table.
if ($type == 'node') {
$access_alias = $query->join('node_access', 'na', '%alias.nid = ' . $nalias . '.nid');
}
else {
$access_alias = $query->leftJoin('node_access', 'na', '%alias.nid = ' . $nalias . '.entity_id');
$base_alias = $nalias;
}
$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($access_alias . '.gid', $gid)
->condition($access_alias . '.realm', $realm)
);
}
}
$count = count($grant_conditions->conditions());
if ($type == 'node') {
if ($count) {
$query->condition($grant_conditions);
}
$query->condition($access_alias . '.grant_' . $op, 1, '>=');
}
else {
if ($count) {
$entity_conditions->condition($grant_conditions);
}
$entity_conditions->condition($access_alias . '.grant_' . $op, 1, '>=');
}
}
}
if ($type == 'entity' && count($entity_conditions->conditions())) {
// All the node access conditions are only for field values belonging to
// nodes.
$entity_conditions->condition("$base_alias.entity_type", 'node');
$or = db_or();
$or->condition($entity_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.
*
* @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".
*/
function domain_check_response($domain, $drush = 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) {
if (empty($drush)) {
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)
);
}
else {
return dt('!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',
'features_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_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 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) {
return db_query('SELECT domain_id FROM {domain_export} WHERE machine_name = :machine_name', array(':machine_name' => $machine_name))->fetchField();
}
/**
* 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('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;
}
/**
* 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 (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 hook_preprocess_page().
*/
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 [canonical-url:path/to/item] 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 [canonical-url:path/to/item] to make the URL point to the canonical path.');
}
/**
* 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;
}