array( 'title' => t('Create subdomains'), 'description' => t('Create new subdomains'), ), 'edit subdomains' => array( 'title' => t('Edit subdomains'), 'description' => t('Edit subdomain for records user user has access to'), 'restrict access' => TRUE, ), ); } /** * Implementation of hook_menu(). */ function subdomain_menu() { $items = array(); $base = array( 'access callback' => 'user_access', 'access arguments' => array('administer site configuration'), ); $items['subdomain/homepage'] = $base + array( 'type' => MENU_CALLBACK, 'page callback' => 'subdomain_view_homepage', 'access callback' => TRUE, ); $items['admin/config/system/subdomain'] = $base + array( 'title' => t('Subdomain Settings'), 'description' => t('Configure subdomain settings.'), 'page callback' => 'subdomain_admin', 'page arguments' => array('subdomain_admin_settings'), 'file' => 'includes/subdomain.admin.inc', 'weight' => 100, ); $items['admin/config/system/subdomain/general'] = $base + array( 'title' => t('General'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items['admin/config/system/subdomain/tools'] = $base + array( 'title' => t('Tools'), 'type' => MENU_LOCAL_TASK, 'page callback' => 'subdomain_tools', 'file' => 'includes/subdomain.admin.inc', 'weight' => 0, ); $items['admin/config/system/subdomain/filters'] = $base + array( 'title' => t('Filters'), 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('subdomain_filters_form'), 'file' => 'includes/subdomain.admin.inc', 'weight' => 0, ); $items['subdomain/validate'] = array( 'type' => MENU_CALLBACK, 'title' => 'Callback only', 'page callback' => 'subdomain_js_validate', 'access callback' => TRUE, ); $items['subdomain/test'] = array( 'type' => MENU_CALLBACK, 'page callback' => '_subdomain_test_request', 'access callback' => TRUE, 'file' => 'includes/subdomain.admin.inc' ); return $items; } /** * Implementation of hook_form_alter(). */ function subdomain_form_alter(&$form, $form_state, $form_id) { // Add subdomain field (if necessary) to appropriate entry form if (subdomain()->is_entry_form($form_id)) { subdomain()->entry_form_alter($form, $form_id); } } function subdomain_user_insert(&$edit, $account, $category) { $edit['uid'] = $account->uid; subdomain()->data_handler('insert', 'user', $edit); } function subdomain_user_update(&$edit, $account, $category) { $edit['uid'] = $account->uid; subdomain()->data_handler('update', 'user', $edit); } function subdomain_user_delete($account) { subdomain()->data_handler('delete', 'user', $account); } function subdomain_node_insert($node) { subdomain()->data_handler('insert', 'group', $node); } function subdomain_node_update($node) { subdomain()->data_handler('update', 'group', $node); } function subdomain_node_delete($node) { subdomain()->data_handler('delete', 'group', $node); } function subdomain_taxonomy_term_insert($term) { subdomain()->data_handler('insert', 'term', $term); } function subdomain_taxonomy_term_update($term) { subdomain()->data_handler('update', 'term', $term); } function subdomain_taxonomy_term_delete($term) { subdomain()->data_handler('delete', 'term', $term); } function subdomain_node_type_insert($info) { subdomain()->data_handler('insert', 'contenttype', $info); } function subdomain_node_type_update($info) { subdomain()->data_handler('update', 'contenttype', $info); } function subdomain_node_type_delete($info) { subdomain()->data_handler('delete', 'contenttype', $info); } /** * Implementation of hook_user(). */ function subdomain_user($op, &$edit, &$account, $category = NULL) { if (subdomain()->mode == 'user') { if ($op == 'validate') { // If new user, check if subdomain exists if (!isset($edit['_account']) && subdomain()->exists($edit[subdomain()->form_field])) { form_set_error($field, t('Subdomain already taken. Please try a different one.')); } } } } /** * Inbound URL rewrite helper * If host includes subdomain, rewrite URI and internal path if necessary */ function subdomain_url_inbound_alter(&$result, $path, $path_language) { static $executed = FALSE; // Only investigate URL on 1st execution if (!$executed) { $executed = TRUE; // Load subdomain engine if not loaded yet subdomain(); // Fetch subdomain ID if ($subdomain = subdomain_get_current_subdomain()) { // Do nothing if subdomain is "www" or reserved if ($subdomain == 'www' && variable_get('subdomain_prepend_www', TRUE) || in_array($subdomain, subdomain_get_reserved_subdomains())) { return; } // Lookup subdomain ID $sid = subdomain_get_sid($subdomain); // If subdomain doesn't exist & non-node path, redirect to base domain // When deleting a subdomain object, this prevents user from ending up on // on page not found. if (empty($sid) && !_subdomain_id_from_path('nid', $path)) { $www = variable_get('subdomain_prepend_www', TRUE) ? 'www.' : ''; header("Location: ". _subdomain_get_protocol() . $www . subdomain_get_domain() . '/'. $path, TRUE, 301); exit; } // If subdomain doesn't exist, send user to page not found // Make exception for test subdomain elseif (empty($sid) && $subdomain != SUBDOMAIN_TEST_SUBDOMAIN) { $result = subdomain_set_request_path('subdomain/notfound/'. $_SERVER['HTTP_HOST'] .'/'. $path); } } // If subdomain and no path, rewrite to subdomain homepage if (!empty($sid) && !subdomain_get_request_path()) { $result = subdomain_set_request_path(subdomain()->get_homepage_path($sid)); } elseif ($nid = _subdomain_id_from_path('nid', $result)) { // If the path is a node path for the current node-based subdomain (e.g. an og group edit path: node/%/edit), do nothing // TODO: Remember/figure-out why this is needed and document! if (subdomain()->type() == 'node' && isset($sid) && $nid == $sid) { // do nothing } // If we're viewing a node, ensure we're on the correct subdomain // Force PageNotFound if user attempting to view content on incorrect subdomain... elseif (subdomain()->get_content_subdomain($nid) != subdomain_get_current_subdomain()) { $result = subdomain_set_request_path('subdomain/notfound/'. $_SERVER['HTTP_HOST'] .'/'. $path); } } } } /** * Outbound URL rewrite helper * Rewrite URLs: attach/remove subdmain as appropriate */ function subdomain_url_outbound_alter(&$path, &$options, $original_path, $clear_path_cache = FALSE) { // Cache outbound paths static $paths = array(); // Clear path cache (sometimes needed when new subdomains created - e.g. og) if ($clear_path_cache) { $paths = array(); } // Process path if not in cache if (!isset($paths[$original_path])) { $www = variable_get('subdomain_prepend_www', TRUE) ? 'www.' : ''; // If front page and we're on a subdomain, strip the subdomain if (($original_path == '' || $original_path == '') && subdomain_get_current_subdomain()) { $paths[$original_path]['path'] = $path; $paths[$original_path]['base_url'] = _subdomain_get_protocol() . $www . subdomain_get_domain(); } // Otherwise, process path and attach/remove subdomain if necessary else { // Determine subdomain if any from original_path, and alter path if necessary subdomain()->url_outbound_helper($subdomain, $path, $original_path, $is_system_path); // Cache $path in case it was altered $paths[$original_path]['path'] = $path; if ($is_system_path && subdomain_get_current_subdomain()) { $paths[$original_path]['base_url'] = _subdomain_get_protocol() . subdomain_get_current_subdomain() .'.'. subdomain_get_domain(); } elseif ($subdomain) { $paths[$original_path]['base_url'] = _subdomain_get_protocol() . $subdomain .'.'. subdomain_get_domain(); } else { $paths[$original_path]['base_url'] = _subdomain_get_protocol() . $www . subdomain_get_domain(); } } } // Set path and options $options['absolute'] = TRUE; $options['base_url'] = $paths[$original_path]['base_url']; $path = $paths[$original_path]['path']; } /** * Clear outbound URL cache */ function subdomain_url_outbound_cache_clear() { subdomain_url_outbound_alter($path, $options = array(), NULL, $clear_path_cache = TRUE); } function subdomain_view_homepage() { $output = ''; if (subdomain()->home == 'view') { // Build argument array for view $args = arg(); // Remove "subdomain/homepage" portions of path array_shift($args); array_shift($args); // Load and execute view $view = views_get_view(subdomain()->view); $output = $view->preview('default', $args); drupal_set_title($view->get_title()); } return $output; } function subdomain_get_reserved_subdomains() { $reserved = variable_get('subdomain_reserved', ''); $reserved = strtolower(str_replace(chr(13) . chr(10), "\n", $reserved)); $reserved = explode("\n", $reserved); // Add internal test subdomain to reserved list array_unshift($reserved, SUBDOMAIN_TEST_SUBDOMAIN); return $reserved; } function subdomain_get_domain() { global $cookie_domain; return trim($cookie_domain, '.'); } /** * Returns current subdomain if any */ function subdomain_get_current_subdomain() { static $subdomain; if (!isset($subdomain)) { $subdomain_length = strpos($_SERVER['HTTP_HOST'], '.'. subdomain_get_domain()); $subdomain = substr($_SERVER['HTTP_HOST'], 0, $subdomain_length); if (variable_get('subdomain_prepend_www', TRUE) && $subdomain == 'www') { $subdomain = ''; } } return $subdomain; } function subdomain_get_sid($subdomain) { static $sids = array(); if (!isset($sids[$subdomain])) { $sids[$subdomain] = db_query("SELECT sid FROM {subdomain} WHERE subdomain = :subdomain", array(':subdomain' => subdomain()->clean($subdomain)))->fetchField(); } return $sids[$subdomain]; } function subdomain_get_subdomain($sid) { static $subdomains = array(); if (!isset($subdomains[$sid])) { $subdomains[$sid] = db_query("SELECT subdomain FROM {subdomain} WHERE sid = :sid", array(':sid' => $sid))->fetchField(); } return $subdomains[$sid]; } /** * JS form validation handler: checks whether subdomain already exists */ function subdomain_js_validate() { $reserved = subdomain_get_reserved_subdomains(); $is_reserved = in_array($_GET['subdomain'], $reserved); $sid = subdomain_get_sid($_GET['subdomain']); // Subdomain is valid if it doesn't exist or it's the one being edited drupal_json_output(array('valid' => !$is_reserved && (empty($sid) || $sid == $_GET['sid']))); exit; } function subdomain_get_request_path() { static $path; if (!isset($path)) { if (variable_get('clean_url', FALSE)) { $path = trim(strtok(request_uri(), '?'), '/'); } else { $path = trim(request_uri(), '/'); } } return $path; } function subdomain_set_request_path($path) { $_GET['q'] = $path; if (isset($_SERVER['REQUEST_URI'])) { if (variable_get('clean_url', FALSE)) { $_SERVER['REQUEST_URI'] = '/'. $path; } else { $_SERVER['REQUEST_URI'] = '/?q='. $path; } } if (empty($path)) { unset($_REQUEST['q']); } else { $_REQUEST['q'] = $path; } return $path; } /** * Checks whether protocol is https or http * Based on code from http://php.net/manual/en/reserved.variables.server.php#89306 */ function _subdomain_get_protocol() { static $protocol; if (!isset($protocol)) { if (isset($_SERVER['https']) && $_SERVER['https'] == 1 /* Apache */ || isset($_SERVER['https']) && $_SERVER['https'] == 'on' /* IIS */ || isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) /* others */ { $protocol = 'https://'; } else { $protocol = 'http://'; } } return $protocol; } function _subdomain_id_from_path($type, $path) { // Borrowed from Domain Access: Advanced pattern matching, we find the node id based on token %n in the path string. static $paths; $default_paths = array( 'nid' => "node/%n\r\nnode/%n/edit\r\ncomment/reply/%n\r\nnode/add/book/parent/%n\r\nbook/export/html/%n\r\nnode/%n/outline", 'uid' => "user/%n\r\nuser/%n/edit", 'tid' => "taxonomy/term/%n", ); $id = NULL; if (!isset($paths[$type])) { $pathdata = variable_get('subdomain_'. $type .'_paths', $default_paths[$type]); $path_match = preg_replace('/(\r\n?|\n)/', '|', $pathdata); $paths[$type] = explode("|", $path_match); } $pattern = explode('/', $path); foreach ($paths[$type] as $match) { $match_array = explode('/', $match); $placeholder = array_search('%n', $match_array); if (isset($pattern[$placeholder])) { $match_array[$placeholder] = $pattern[$placeholder]; if (is_numeric($pattern[$placeholder]) && $match_array == $pattern) { $id = (int) $pattern[$placeholder]; break; } } } return $id; } function _subdomain_get_custom_form_field($default, $editable = FALSE) { if ($editable) { // Make subdomain entry required unless user is site admin... return array( '#type' => 'textfield', '#title' => t('Choose a subdomain'), '#description' => t('Letters and underscores only. Choose carefully, once set, this cannot be changed.'), '#required' => !user_access('administer site configuration'), '#weight' => -5, '#default_value' => $default, ); } // If user can't create subdomains, don't show this... elseif (user_access('create subdomains')) { return array( '#type' => 'item', '#title' => subdomain()->static_form_text(), '#weight' => -5, '#value' => 'http://'. $default .'.'. subdomain_get_domain(), ); } } function _subdomain_add_js_validation($sid = NULL) { drupal_add_js(array('subdomain' => array('selector' => "edit-". subdomain()->form_field, 'sid' => $sid)), 'setting'); } /** * Stores and retrieves subdomain form values * Currently used for contenttype form, b/c additional form values aren't passed through * to hook_node_type(). */ function _subdomain_cached_form_values($form_values = NULL) { static $cached_form_values; if (!empty($form_values)) { $cached_form_values = $form_values; } return $cached_form_values; } function _subdomain_filtered_content_types() { static $filtered; if (!isset($filtered)) { // Filter "0" values (i.e. unchecked content types) from array $filtered = array_filter(variable_get('subdomain_filter_contenttypes', array())); if (!count($filtered)) { // TODO: Review this hack. Necessary, because PDO chokes on empty parenthesis parameters (e.g. SELECT * FROM node WHERE type NOT IN ()) // Empty string works, NULL doesn't $filtered = array(''); } } return $filtered; } function _subdomain_validate_subdomain($form, &$form_state) { if ($subdomain = subdomain()->clean($form_state['values'][subdomain()->form_field])) { if (in_array($subdomain, subdomain_get_reserved_subdomains())) { form_set_error(subdomain()->form_field, t('The '. subdomain()->form_field .' you entered is reserved and cannot be used.')); } } }