t('Domain access'), 'path' => 'admin/build/domain', 'access' => $admin, 'callback' => 'domain_admin', 'callback arguments' => array('configure'), 'description' => t('Settings for the Domain Access module.') ); $items[] = array( 'title' => t('Settings'), 'path' => 'admin/build/domain/settings', 'access' => $admin, 'type' => MENU_DEFAULT_LOCAL_TASK, 'callback' => 'domain_admin', 'callback arguments' => array('configure'), 'weight' => -10 ); $items[] = array( 'title' => t('Domain list'), 'path' => 'admin/build/domain/view', 'access' => $admin, 'type' => MENU_LOCAL_TASK, 'callback' => 'domain_admin', 'callback arguments' => array('view'), 'weight' => -8 ); $items[] = array( 'title' => t('Create domain record'), 'path' => 'admin/build/domain/create', 'access' => $admin, 'type' => MENU_LOCAL_TASK, 'callback' => 'domain_admin', 'callback arguments' => array('create'), 'weight' => -4 ); $items[] = array( 'title' => t('Node settings'), 'path' => 'admin/build/domain/advanced', 'access' => $admin, 'type' => MENU_LOCAL_TASK, 'callback' => 'domain_admin', 'callback arguments' => array('advanced'), 'weight' => -2 ); } else { $items[] = array( 'title' => t('Edit domain record'), 'path' => 'admin/build/domain/edit', 'access' => $admin, 'type' => MENU_CALLBACK, 'callback' => 'domain_admin', 'callback arguments' => array('edit', arg(4)) ); $items[] = array( 'title' => t('Delete domain record'), 'path' => 'admin/build/domain/delete', 'access' => $admin, 'type' => MENU_CALLBACK, 'callback' => 'domain_admin', 'callback arguments' => array('delete', arg(4)) ); } return $items; } /** * Implements hook_perm() * * @ingroup drupal */ function domain_perm() { $rule = variable_get('domain_editors', DOMAIN_EDITOR_RULE); if ($rule) { return array('administer domains', 'set domain access', 'edit domain nodes'); } else { return array('administer domains', 'set domain access'); } } /** * Implements hook_block() * * A nifty little domain-switcher block, useful during debugging. * * @ingroup drupal */ function domain_block($op = 'list', $delta = 0, $edit = array()) { global $_domain, $base_url; $blocks = array(); switch ($op) { case 'list': $blocks[0] = array( 'info' => t('Domain switcher'), ); return $blocks; break; case 'view': $block['subject'] = t('Domain switcher'); $items = array(); $domains = domain_domains(); $msg = FALSE; foreach ($domains as $domain) { if ($domain['valid']) { $title = $domain['sitename']; $allow = TRUE; } else { $title = $domain['sitename'] .' *'; $allow = FALSE; if (user_access('administer domains')) { $msg = TRUE; $allow = TRUE; } } if ($allow) { $items[] = l($title, domain_get_uri($domain)); } } $block['content'] = theme('item_list', $items); if ($msg) { $block['content'] .= t('* Inactive domain.'); } return $block; break; } } /** * Implements hook_user() * * Attached domain_id records to all registering users. These * are used to determine which 'domain_editor' group that users * with the 'edit domain nodes' permission are in. * * @ingroup drupal */ function domain_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'form': case 'register': global $_domain; $result = db_query("SELECT domain_id, subdomain, sitename, scheme FROM {domain}"); $options = array(); // By default, the requesting domain is assigned. if (empty($account->domain_user)) { ($_domain['domain_id'] == 0) ? $default = array(-1) : $default = array($_domain['domain_id']); } else { $default = $account->domain_user; } $options[-1] = variable_get('domain_sitename', variable_get('sitename', 'Drupal')); while ($data = db_fetch_array($result)) { $options[$data['domain_id']] = $data['sitename']; } if (user_access('set domain access')) { $form['domain_user'] = array( '#type' => 'fieldset', '#title' => t('Domain access'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#weight' => 1 ); $form['domain_user']['domain_user'] = array( '#type' => 'checkboxes', '#options' => $options, '#title' => t('Domain access setttings'), '#description' => t('Select the affiliates that this user belongs to. Used to grant editing permissions for users with the "edit domain nodes" permission.'), '#default_value' => $default ); } else { $form['domain_user'] = array( '#type' => 'value', '#value' => $default ); } return $form; break; case 'validate': return array('domain_user' => $edit['domain_user']); break; case 'view': if (user_access('set domain access') && !empty($account->domain_user)) { $output = ''; $items['domain'] = array('title' => t('Domain settings'), 'value' => $output, ); return array(t('Domain status') => $items); } break; } } /** * Implements hook_cron() * * This function invokes hook_domaincron() and allows * Domain Access modules to run functions for all active affiliates. * * @ingroup drupal */ function domain_cron() { global $_domain; // Check to see if this function is needed at all. $modules = module_implements('domaincron'); if (!empty($modules)) { // Store the current $_domain global. $_temp = $_domain; // Get the domain list. $domains = domain_domains(); // Run the hook for each active domain. foreach ($domains as $domain) { // Set the global to the current $domain. $_domain = $domain; foreach ($modules as $module) { module_invoke($module, 'domaincron', $domain); } } // Set the $_domain global back. $_domain = $_temp; } } /** * Router function to call various administration tasks * * @param $action * The function to be performed. * @param $id * The domain_id of the record to be acted upon. * * @ingroup domain */ function domain_admin($action, $id = NULL) { include_once('domain_admin.inc'); $func = 'domain_'. $action; return $func($id); } /** * Runs a lookup against the {domain} table. One of the two values must be present * * This function also calls hook_domainload(), which lets module developers overwrite * or add to the $domain array. * * @param $domain_id * The domain_id taken from {domain}. Optional. * @param $subdomain * The string representation of a {domain} entry. Optional. * @return * An array containing the requested row from the {domain} table, plus the * elements added by hook_domainload(). Returns -1 on failure. * * @ingroup domain */ function domain_lookup($domain_id = NULL, $subdomain = NULL) { static $domains; // If both are NULL, no lookup can be run. if (is_null($domain_id) && is_null($subdomain)) { return -1; } // Create a unique key so we can static cache all requests. else { $key = $domain_id . $subdomain; } if (!isset($domains[$key])) { if ($domain_id === 0) { $domain = domain_default(); } else if ($domain_id) { $domain = db_fetch_array(db_query("SELECT domain_id, subdomain, sitename, scheme, valid FROM {domain} WHERE domain_id = %d", $domain_id)); } else if ($subdomain) { $domain = db_fetch_array(db_query("SELECT domain_id, subdomain, sitename, scheme, valid FROM {domain} WHERE subdomain = '%s'", $subdomain)); } if (!is_null($domain['domain_id'])) { // Let submodules overwrite the defaults, if they wish. $extra = module_invoke_all('domainload', $domain); $domain = array_merge($domain, $extra); $domains[$key] = $domain; } else { $domains[$key] = -1; } } return $domains[$key]; } /** * Assigns the default settings to domain 0, the root domain. * * This value is used throughout the modules, so needed abstraction. * * @ingroup domain */ function domain_default() { static $default; if (empty($default)) { $default['domain_id'] = 0; $default['sitename'] = variable_get('domain_sitename', variable_get('sitename', 'Drupal')); $default['subdomain'] = variable_get('domain_root', ''); $default['scheme'] = variable_get('domain_scheme', 'http'); // Set the valid flag. $default['valid'] = TRUE; // Let submodules overwrite the defaults, if they wish. $extra = module_invoke_all('domainload', $default); $default = array_merge($default, $extra); } return $default; } /** * Return all active domains (including the default) as an array. * * @ingroup domain */ function domain_domains() { static $domains; if (empty($domains)) { $domains = array(); $domains[] = domain_default(); // Query the db for active domain records. $result = db_query("SELECT domain_id FROM {domain}"); while ($data = db_fetch_array($result)) { $domain = domain_lookup($data['domain_id']); $domains[] = $domain; } } $sort = variable_get('domain_sort', 'id'); uasort($domains, '_domain_'. $sort .'_sort'); return $domains; } /** * Helper sort function * * @ingroup domain */ function _domain_id_sort($a, $b) { return ($a['domain_id'] < $b['domain_id']) ? -1 : 1; } /** * Helper sort function * * @ingroup domain */ function _domain_name_sort($a, $b) { return strcmp($a['sitename'], $b['sitename']); } /** * Helper sort function * * @ingroup domain */ function _domain_url_sort($a, $b) { return strcmp($a['subdomain'], $b['subdomain']); } /** * Helper sort function * * @ingroup domain */ function _domain_rid_sort($a, $b) { return ($a['domain_id'] > $b['domain_id']) ? -1 : 1; } /** * Helper sort function * * @ingroup domain */ function _domain_rname_sort($a, $b) { return strcmp($b['sitename'], $a['sitename']); } /** * Helper sort function * * @ingroup domain */ function _domain_rurl_sort($a, $b) { return strcmp($a['subdomain'], $b['subdomain']); } /** * Implements hook_domainload() * * Adds the home page 'path' and 'site_grant' boolean. * * @ingroup domain */ function domain_domainload(&$domain) { // Get the path to the home page for this domain. $domain['path'] = domain_get_path($domain); // Grant access to all affiliates. $domain['site_grant'] = DOMAIN_SITE_GRANT; } /** * Determine an absolute path for a domain * * @param $domain * The currently active $domain array, provided by domain_lookup(). * * @ingroup domain */ function domain_get_path($domain) { global $base_url; $_url = parse_url($base_url); // If in a subfolder, the web server needs the trailing slash. (!empty($_url['path'])) ? $_path = $_url['path'] .'/' : $_path = $_url['path']; $path = $domain['scheme'] .'://'. $domain['subdomain'] . $_path; return $path; } /** * Determine a relative path to the current page */ function domain_get_uri($domain) { global $base_url; $_url = parse_url($base_url); $path = $domain['scheme'] .'://'. $domain['subdomain'] . request_uri(); return $path; } /** * Determine if we must switch the active domain. * * This function will execute a drupal_goto() to pop users to the correct * domain. * * @param $domain * The currently active $domain array, provided by domain_lookup(). * * @ingroup domain */ function domain_goto($domain) { global $_domain; // We must be on the proper domain, see http://drupal.org/node/186153. if ($domain != -1 && $_domain['domain_id'] != $domain['domain_id']) { $path = domain_get_uri($domain); drupal_goto($path); } } /** * Implements hook_nodeapi(). * * This function is used to provide debugging information and to prep values from * the {node_access} table when editing nodes. Since not all users can see the * domain access editing checkboxes, we pass some node_access values as hidden elements. * * @ingroup node */ function domain_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { switch ($op) { case 'prepare': case 'load': // Append the domain grants to the node for editing. $node->domains = array(); $node->editors = array(); $node->domain_site = FALSE; $node->url = 'test'; $result = db_query("SELECT gid, realm, grant_view, grant_update, grant_delete FROM {node_access} WHERE nid = %d AND (realm = '%s' OR realm = '%s' OR realm = '%s')", $node->nid, 'domain_id', 'domain_site', 'domain_editor'); while ($data = db_fetch_object($result)) { // Transform the 0 to -1, since {node_access} is unsigned. ($data->gid == 0) ? $gid = -1 : $gid = $data->gid; if ($data->realm == 'domain_id') { $node->domains[] = $gid; if ($gid > 0) { $domain = domain_lookup($gid); $node->subdomains[] = $domain['sitename']; } else { $node->subdomains[] = variable_get('domain_sitename', variable_get('sitename', 'Drupal')); } } else if ($data->realm == 'domain_site') { $node->domain_site = TRUE; $node->subdomains[] = t('All affiliates'); } else if ($data->realm == 'domain_editor') { $node->domain_editor = TRUE; if ($gid > 0) { $domain = domain_lookup($gid); $node->editors[] = $domain['sitename']; } else { $node->editors[] = variable_get('domain_sitename', variable_get('sitename', 'Drupal')); } } } break; case 'view': // Search module casts both $a3 and $a4 as FALSE, not NULL. // We check that to hide this data from search and other nodeapi // calls that are neither a teaser nor a page view. if ($a3 !== FALSE || $a4 !== FALSE) { $output = ''; $debug = variable_get('domain_debug', 0); if ($debug && user_access('set domain access')) { if (!empty($node->subdomains)) { $output .= '

Subdomains

'; $node->content['subdomains'] = array('#value' => $output, '#weight' => 20); } if (!empty($node->editors)) { $output = '

Editors

'; $node->content['editors'] = array('#value' => $output, '#weight' => 21); } if (empty($output)) { $node->content['domain'] = array('#value' => t('This node is not assigned to a domain.'), '#weight' => 22); } } } break; } } /** * 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. * * @ingroup node */ function domain_node_grants($account, $op) { global $_domain; // Do we need to use complex rules? $rules = variable_get('domain_access_rules', FALSE); // By design, all users can see content sent to all affiliates, // but the $_domain['site_grant'] can be set to FALSE. if ($op == 'view') { if ($_domain['site_grant']) { $grants['domain_site'][] = 0; if ($rules) { $grants['domain_site']['group'] = 'domain'; } } // Grant based on active subdomain. $grants['domain_id'][] = $_domain['domain_id']; if ($rules) { $grants['domain_id']['group'] = 'domain'; } // In special cases, we grant the ability to view all nodes. That is, // we simply get out of the way of other node_access rules. // We do this with the universal 'domain_all' grant. if ($op == 'view' && domain_grant_all()) { // If no other node access modules are present, return our grant. // Otherwise, we just step out of the way. if ($rules) { return array(); } else { return array('domain_all' => array(0)); } } } else { // Special permissions for editors $editors = variable_get('domain_editors', DOMAIN_EDITOR_RULE); if ($editors && user_access('edit domain nodes', $account)) { if (!empty($account->domain_user)) { foreach ($account->domain_user as $id) { if (abs($id) > 0) { if ($id > 0) { $grants['domain_editor'][] = $id; } else { $grants['domain_editor'][] = 0; } // Advanced rules let us access check unpublished nodes for editing. if ($rules) { $grants['domain_editor']['check'] = TRUE; } } } } } } // Let Domain Access module extensions act to override the defaults. static $_modules; if (!isset($_modules)) { $_modules = module_implements('domaingrants'); } if (!empty($_modules)) { foreach ($_modules as $module) { // Cannot use module_invoke_all() since these are passed by reference. $function = $module . '_domaingrants'; $function($grants, $account, $op); } } return $grants; } /** * Implements hook_node_access_records() * * Set permissions for a node to be written to the database. By design * if no options are selected, the node is assigned to the main site. * * @ingroup node */ function domain_node_access_records($node) { if (domain_disabling()) { return; } // Check to see if the node_access patch is enabled. If it is, then // we need to know if more than one node_access module is running. if (function_exists('node_access_grants_sql') && count(module_implements('node_grants')) > 1) { variable_set('domain_access_rules', TRUE); } // How is core content handled for this site? We need to run this // check when the module is first enabled. $behavior = variable_get('domain_behavior', DOMAIN_INSTALL_RULE); if (domain_enabling() && $behavior == 1) { $node->domain_site = TRUE; } $grants = array(); // If the form is hidden, we are passed the 'domains_raw' variable. if (is_array($node->domains_raw)) { $node->domains = array(); foreach ($node->domains_raw as $value) { $node->domains[$value] = $value; } } // If set, grant access to the core site, otherwise // The grant must be explicitly given to a domain. if ($node->domain_site) { $grants[] = array( 'realm' => 'domain_site', 'gid' => 0, 'grant_view' => TRUE, 'grant_update' => FALSE, 'grant_delete' => FALSE, 'priority' => 0, // If this value is > 0, then other grants will not be recorded ); } // Special permissions for editors, if activated. $editors = variable_get('domain_editors', DOMAIN_EDITOR_RULE); if (!empty($node->domains)) { foreach($node->domains as $key => $value) { // We can't use a 0 value in an $options list, so convert -1 to 0. if (abs($value) > 0) { ($key == -1) ? $key = 0 : $key = $key; $grants[] = array( 'realm' => 'domain_id', 'gid' => $key, 'grant_view' => TRUE, 'grant_update' => FALSE, 'grant_delete' => FALSE, 'priority' => 0, ); if ($editors) { $grants[] = array( 'realm' => 'domain_editor', 'gid' => $key, 'grant_view' => FALSE, 'grant_update' => TRUE, 'grant_delete' => TRUE, 'priority' => 0, ); } } } } // At least one option must be present, and it is the default site // this prevents null values in the form. // There is probably a more elegant way to do this, earlier in the routine. if (empty($grants)) { $grants[] = array( 'realm' => 'domain_id', 'gid' => 0, 'grant_view' => TRUE, 'grant_update' => FALSE, 'grant_delete' => FALSE, 'priority' => 0, ); if ($editors) { $grants[] = array( 'realm' => 'domain_editor', 'gid' => 0, 'grant_view' => FALSE, 'grant_update' => TRUE, 'grant_delete' => TRUE, 'priority' => 0, ); } } // Let Domain Access module extensions act to override the defaults. static $_modules; if (!isset($_modules)) { $_modules = module_implements('domainrecords'); } if (!empty($_modules)) { foreach ($_modules as $module) { // Cannot use module_invoke_all() since these are passed by reference. $function = $module . '_domainrecords'; $function($grants, $node); } } return $grants; } /** * Upon enabling this module, store the default view grant * in the {node_access} table. * @see domain_grant_all() * * @ingroup domain */ function domain_enable() { domain_enabling(TRUE); $check = db_result(db_query("SELECT COUNT(nid) FROM {node_access} WHERE realm = 'domain_all' AND gid = 0")); if (!$check) { db_query("INSERT INTO {node_access} VALUES (0, 0, 'domain_all', 1, 0, 0)"); } } /** * Writes the default grants when the module is frist enabled. * * @ingroup node */ function domain_enabling($set = NULL) { static $enabling = FALSE; if ($set !== NULL) { $enabling = $set; } return $enabling; } /** * Implements hook_disable() * * @ingroup node */ function domain_disable() { domain_disabling(TRUE); // Delete our all access row from {node_access}. db_query("DELETE FROM {node_access} WHERE realm = 'domain_all' AND gid = 0"); // This lets us control how the module updates the {node_access} table. variable_del('domain_behavior'); variable_del('domain_editors'); node_access_rebuild(); } /** * Simple function to make sure we don't respond with grants when disabling ourselves. * * @ingroup node */ function domain_disabling($set = NULL) { static $disabling = FALSE; if ($set !== NULL) { $disabling = $set; } return $disabling; } /** * 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. * * @ingroup node */ function domain_form_alter($form_id, &$form) { // Apply to all node editing forms, but make sure we are not on the CCK field configuration form. if ($form['#id'] == 'node-form' && !$form['#node']->cck_dummy_node_form) { global $_domain; // By default, the requesting domain is assigned. $default = array($_domain['domain_id']); // How is core content handled for this site? $behavior = variable_get('domain_behavior', DOMAIN_INSTALL_RULE); if ($behavior == 1 || $_domain['domain_id'] == 0) { $default[] = -1; } $options = array(); foreach (domain_domains() as $data) { // Cannot pass zero in checkboxes. ($data['domain_id'] == 0) ? $key = -1 : $key = $data['domain_id']; // The domain must be valid. if ($data['valid'] || user_access('administer domains')) { $options[$key] = $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 ); $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.'), '#default_value' => ($form['#node']->nid) ? $form['#node']->domain_site : variable_get('domain_node_'. $form['#node']->type, $behavior), ); $form['domain']['domains'] = array( '#type' => 'checkboxes', '#title' => t('Publish to'), '#options' => $options, '#required' => TRUE, '#description' => t('Select which affiliates can access this content.'), '#default_value' => ($form['#node']->nid) ? $form['#node']->domains : $default, ); } else { $form['domain_site'] = array( '#type' => 'value', '#value' => ($form['#node']->nid) ? $form['#node']->domain_site : variable_get('domain_node_'. $form['#node']->type, $behavior), ); // Name this element for special handling. $form['domains_raw'] = array( '#type' => 'value', '#value' => ($form['#node']->nid) ? $form['#node']->domains : $default ); } // THIS SECTION BREAKS CCK if we don't check for cck_dummy_node_form! See http://drupal.org/node/186624 // Some editors cannot administer nodes, so we have to add these form elements. if (variable_get('domain_editors', DOMAIN_EDITOR_RULE) == 1 && user_access('edit domain nodes')) { $access = variable_get('domain_form_elements', array('options', 'delete', 'comment_settings', 'path')); foreach ($access as $item) { $form[$item]['#access'] = TRUE; } } } } /** * Implements hook_url_alter(). * * Forces absolute paths for domains when needed. * We run this in all cases, if we exclude $absolute = TRUE, node_search fails. */ function domain_url_alter(&$path, &$query, &$fragment, &$absolute) { global $_domain; // Set static variables for the node lookups, to remove redundant queries. static $domain_site, $domain; // This routine only needs to be run from certain urls. $check = domain_grant_all(); if ($check) { // Check to see if this is a node or comment link and set $nid accordingly. // We static the $nid results to make this more efficient. $pattern = explode('/', $path); // Known paths that use the format 'node/NID'. $match = array('node', 'print', 'forward'); if ((in_array($pattern[0], $match) && is_numeric($pattern[1])) || ($pattern[0] == 'comment' && $pattern[1] == 'reply' && is_numeric($pattern[2]))) { // Get the node id as $nid. $nid = $pattern[1]; if ($pattern[0] == 'comment') { $nid = $pattern[2]; } // Remove redundancy from the domain_site check. if (!isset($domain_site[$nid])) { // If this check works, we don't need to rewrite the path. $domain_site[$nid] = db_result(db_query("SELECT grant_view FROM {node_access} WHERE nid = %d AND gid = 0 AND realm = '%s'", $nid, 'domain_site')); } if (!$domain_site[$nid]) { // Remove rendundancy from the domain_id check. if (!isset($domain[$nid])) { // Load the domain data for this node -- but we're only taking the first match. $id = db_result(db_query_range("SELECT gid FROM {node_access} WHERE nid = %d AND realm = '%s' AND grant_view = 1", $nid, 'domain_id', 0, 1)); $domain[$nid] = domain_lookup($id); } // Can we and do we need to rewrite this path? if ($domain[$nid] != -1 && $domain[$nid]['domain_id'] != $_domain['domain_id']) { $absolute = TRUE; $path = $domain[$nid]['path'] . $path; } } } } } /** * Activate the hidden grant for searches. * * @ingroup domain */ function domain_grant_all() { static $grant; if (!isset($grant)) { $search = variable_get('domain_search', 0); // Proof of concept. if ((arg(0) == 'search' && $search) || arg(0) == 'mysite' || (arg(0) == 'user' && arg(2) == 'track')) { $grant = TRUE; } else { $grant = FALSE; } } return $grant; }