module_exists('context_ui') ? MENU_LOCAL_TASK : MENU_NORMAL_ITEM, 'title' => t('Context prefix'), 'description' => t('Settings for context prefix.'), 'path' => 'admin/build/context/prefix', 'callback' => 'drupal_get_form', 'callback arguments' => array('context_prefix_settings_form'), 'access' => user_access('administer site configuration'), 'weight' => 10, ); $items[] = array( 'type' => MENU_DEFAULT_LOCAL_TASK, 'title' => t('Settings'), 'path' => 'admin/build/context/prefix/settings', 'callback arguments' => array('context_prefix_settings_form'), 'access' => user_access('administer site configuration'), 'weight' => 0, ); $items[] = array( 'type' => MENU_LOCAL_TASK, 'title' => t('Registered prefixes'), 'path' => 'admin/build/context/prefix/list', 'callback' => 'context_prefix_admin', 'access' => user_access('administer site configuration'), 'weight' => 10, ); } return $items; } /** * Implementation of hook_init() * Checks for any valid context prefixes in request string and sets the context appropriately */ function context_prefix_init() { static $once; if (!$once) { _context_prefix_init(CONTEXT_PREFIX_PATH); _context_prefix_init(CONTEXT_PREFIX_DOMAIN); _context_prefix_init(CONTEXT_PREFIX_PAIR); $once = true; } } /** * Helper function to initialize, parse + set prefixed contexts. */ function _context_prefix_init($method = CONTEXT_PREFIX_PATH) { switch ($method) { case CONTEXT_PREFIX_PATH: $q = isset($_REQUEST["q"]) ? trim($_REQUEST["q"], "/") : ''; $parsed = context_prefix_parse(CONTEXT_PREFIX_PATH, $q); break; case CONTEXT_PREFIX_PAIR: $q = isset($_REQUEST["q"]) ? trim($_REQUEST["q"], "/") : ''; $parsed = context_prefix_parse(CONTEXT_PREFIX_PAIR, $q); break; case CONTEXT_PREFIX_DOMAIN: $host = $_SERVER['HTTP_HOST']; // We handle sub.domain.com, and nothing more (no sub1.sub2.domain.com). $q = str_replace('http://','',$host); $parsed = context_prefix_parse(CONTEXT_PREFIX_DOMAIN, $q); break; } // if $_GET and $_REQUEST are different, the path has NOT been // aliased. We may need to rewrite the path. if (in_array($method, array(CONTEXT_PREFIX_PATH, CONTEXT_PREFIX_PAIR)) && ($_GET['q'] == $_REQUEST['q'])) { $q = context_prefix_unprefix($q, $method); // there is nothing beyond the path prefix -- treat as frontpage if ($q == '') { $_GET['q'] = variable_get('site_frontpage', 'node'); } // pass the rest of the path onto Drupal cleanly else { $_REQUEST['q'] = $_GET['q'] = _context_prefix_get_normal_path($q); } } if (is_array($parsed)) { foreach ($parsed as $prefix => $info) { context_prefix_set($method, $prefix, $info); } } } /** * Jose's very smart collision avoidance */ if (!function_exists('custom_url_rewrite')) { function custom_url_rewrite($type, $path, $original) { return context_prefix_url_rewrite($type, $path, $original); } } /** * Rewrites path with current context and removes context if searching for source path */ function context_prefix_url_rewrite($type, $path, $original) { $working_path = $path; // preserve original path $args = array(); // Check to see whether url rewriting has been disabled for this one // instance -- currently only possible through cl() if (!clswitch('get')) { $active_path_prefixes = array(); // Retrieve the path prefixes for the current page that were // "stripped out" and write them back into url paths. foreach (context_prefix_get(CONTEXT_PREFIX_PAIR) as $item) { $active_path_prefixes[] = $item['prefix'] .'/'. $item['id']; } foreach (context_prefix_get(CONTEXT_PREFIX_PATH) as $item) { $active_path_prefixes[] = $item['prefix']; } if (count($active_path_prefixes)) { $parsed = context_prefix_parse(CONTEXT_PREFIX_PATH, $working_path) + context_prefix_parse(CONTEXT_PREFIX_PAIR, $working_path); // A "normal" url was requested -- prefix the path if (!$options['alias'] && !strpos($path, '://') && !count($parsed) && count($active_path_prefixes)) { $args = $args + $active_path_prefixes; } } } if ($working_path) { $args[] = $working_path; } return is_array($args) ? implode('/', $args) : ''; } /** * Queries the database & modules for valid prefixes based on prefixing method. * * Modules that wish to provide in-code prefixes should implement the * hook_context_prefix_prefixes(). Which should return an array of prefixes by * by provider. * * For example: * * return array( * 'my_module => array( * array('prefix' => 'foo', 'id' => 1), * array('prefix' => 'bar', 'id' => 2), * ), * ); */ function context_prefix_prefixes($requested_method = CONTEXT_PREFIX_PATH, $reset = FALSE) { static $prefixes; if (!isset($prefixes) || $reset) { $prefixes = array(); // Invoke context_prefix_prefixes() and gather all prefixes // provided "in code" (or stored by their respective modules) foreach (module_invoke_all('context_prefix_prefixes') as $provider => $items) { $method = variable_get('context_prefix_method_'. $provider, CONTEXT_PREFIX_PATH); // If using a prefix pair we don't need to cache the valid prefixes. if ($method == CONTEXT_PREFIX_PAIR) { $prefix = variable_get('context_prefix_method_'. $provider .'_key', false); if ($prefix != false) { $prefixes[$method][$prefix] = array( 'provider' => $provider, 'id' => null, ); } } else { foreach ($items as $item) { if ($item['prefix'] && $item['id']) { $prefixes[$method][$item['prefix']] = array( 'provider' => $provider, 'id' => $item['id'], ); } } } } // Gather database prefixes. $result = db_query("SELECT DISTINCT(provider) FROM {context_prefix}"); while ($item = db_fetch_object($result)) { $method = variable_get('context_prefix_method_'. $item->provider, CONTEXT_PREFIX_PATH); // Don't load all data base prefixes for keyed pairs. if ($method == CONTEXT_PREFIX_PAIR) { $prefix = variable_get('context_prefix_method_'. $item->provider .'_key', false); if ($prefix != false) { $prefixes[$method][$prefix] = array( 'provider' => $item->provider, 'id' => null, ); } } else { $result2 = db_query("SELECT * FROM {context_prefix} WHERE provider = '%s'", $item->provider); while ($row = db_fetch_object($result2)) { $prefixes[$method][$row->prefix] = array( 'provider' => $row->provider, 'id' => $row->id, ); } } } } return (isset($prefixes[$requested_method]) ? $prefixes[$requested_method] : array()); } /** * Parses a query string of various types (url, domain, etc.) and * returns an array of any found prefixes and their respective * providers/id values. */ function context_prefix_parse($method = CONTEXT_PREFIX_PATH, $q) { static $cache; if (!isset($cache[$method][$q])) { $valid_prefixes = context_prefix_prefixes($method); // Parse the provided query string and provide an array of any prefixes found switch ($method) { case CONTEXT_PREFIX_PATH: case CONTEXT_PREFIX_PAIR: $parsed = array(); $args = explode('/', $q); $arg = $args[0]; while (isset($valid_prefixes[$arg])) { $parsed[$arg] = $valid_prefixes[$arg]; array_shift($args); if ($method == CONTEXT_PREFIX_PAIR) { $parsed[$arg]['id'] = array_shift($args); } $arg = $args[0]; if (in_array($arg, $parsed)) { break; } } $cache[$method][$q] = $parsed; break; case CONTEXT_PREFIX_DOMAIN: $parsed = array(); if (isset($valid_prefixes[$q])) { $parsed[$q] = $valid_prefixes[$q]; } $cache[$method][$q] = $parsed; break; } } return $cache[$method][$q]; } /** * Removes any prefixes from a query string. For path prefixes only. */ function context_prefix_unprefix($q, $method, $providers = array()) { $parsed = context_prefix_parse($method, $q); if (is_array($providers) && count($providers)) { foreach ($parsed as $prefix => $info) { if (!in_array($info['provider'], $providers)) { unset($parsed[$prefix]); } } } $parsed = array_keys($parsed); $args = explode('/', $q); switch ($method) { case CONTEXT_PREFIX_PATH: $args = array_diff($args, $parsed); break; case CONTEXT_PREFIX_PAIR: foreach ($parsed as $v) { array_splice($args, array_search($v, $args), 2); } break; } return implode('/', $args); } /** * Invokes hook_context_prefix_provider() to gather all providers. * * Modules that implement hook_context_prefix_provider need to return an * array of prefix definitions. Each definition should have the following * keys: * - name * - description * - callback * - example * * See the spaces module for an usage example. */ function context_prefix_providers($by_method = FALSE) { static $providers; if (!is_array($providers)) { $providers = array(); $providers = module_invoke_all('context_prefix_provider'); } if ($by_method) { static $methods; if (!isset($methods)) { $methods = new context_prefix_cache(); foreach ($providers AS $id => $provider) { $methods->add(variable_get('context_prefix_method_'. $id, CONTEXT_PREFIX_PATH), array($id => $provider)); } } return $methods->get(); } else { return $providers; } } /** * Taken from i18n */ function _context_prefix_get_normal_path($path) { // If bootstrap, drupal_lookup_path is not defined if (!function_exists('drupal_get_headers')) { return $path; } // Check alias without lang elseif ($alias = drupal_lookup_path('source', $path)) { return $alias; } else { return $path; } } /** * Static cache function for setting + storing any prefixed contexts * that are present on this page's request. */ function _context_prefix_set($op = 'set', $type = CONTEXT_PREFIX_PATH, $prefix = '', $info = array()) { static $used; if (!$used) { $used = new context_prefix_cache(); } switch ($op) { case 'set': // Store prefix for url rewriting later on in the stack $info['prefix'] = $prefix; $used->add($type, $info, false); // Fire the provider callback if ($info['provider'] && $info['id']) { // Fire the provider callback $providers = context_prefix_providers(); $callback = $providers[$info['provider']]['callback']; $args = isset($providers[$info['provider']]['callback arguments']) ? $providers[$info['provider']]['callback arguments'] : array(); $args[] = $info['id']; if (function_exists($callback)) { call_user_func_array($callback, $args); } } break; case 'get': if ($type === 'all') { return $used->get(); } else { return $used->get($type); } } } /** * Set wrapper for _context_prefix_set() */ function context_prefix_set($type = CONTEXT_PREFIX_PATH, $prefix = '', $info = array()) { return _context_prefix_set('set', $type, $prefix, $info); } /** * Get wrapper for _context_prefix_set() */ function context_prefix_get($type = CONTEXT_PREFIX_PATH) { return _context_prefix_set('get', $type); } /** * PAGE CALLBACKS ===================================================== */ /** * Page callback for the context_prefix administration page. */ function context_prefix_admin() { global $pager_page_array, $pager_total, $pager_total_items; $page = isset($_GET['page']) ? $_GET['page'] : 0; $element = 0; $limit = 20; // Convert $page to an array, used by other functions. $pager_page_array = array($page); $methods = _context_ui_options(); $merged = array(); foreach(array_keys($methods) as $method) { foreach(context_prefix_prefixes($method) as $prefix => $info) { $info['prefix'] = $prefix; $merged[] = $info; } } $rows =array(); for ($i = $page * $limit; $i < ($page+1) * $limit && $i < count($merged); $i++) { $rows[] = array( $merged[$i]['provider'], $merged[$i]['prefix'], $merged[$i]['id'], $methods[variable_get('context_prefix_method_'. $merged[$i]['provider'], CONTEXT_PREFIX_PATH)], ); } // We calculate the total of pages as ceil(items / limit). $pager_total_items[$element] = count($merged); $pager_total[$element] = ceil($pager_total_items[$element] / $limit); $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1)); if ($rows) { $output .= theme('table', array(t('Provider'), t('Prefix'), t('ID'), t('Method')), $rows); $output .= theme('pager'); } else { $output .= "

". t('No context prefixes have been registered.') ."

"; } return $output; } /** * Settings form for choosing the operating mode of context_prefix */ function context_prefix_settings_form() { global $base_url; $form = array(); $options = _context_ui_options(); foreach (context_prefix_providers() as $id => $provider) { // Check to see whether provider has limited the available prefixing methods if (is_array($provider['methods']) && count($provider['methods'])) { $provider_options = array(); foreach ($provider['methods'] as $method) { $provider_options[$method] = $options[$method]; } } else { $provider_options = $options; } $form[$id] = array( '#fieldset' => true, '#provider' => true, '#title' => $provider['name'], '#description' => $provider['description'], ); $form[$id]['context_prefix_method_'. $id] = array( '#title' => t('Method'), '#type' => 'select', '#options' => $provider_options, '#default_value' => variable_get('context_prefix_method_'. $id, CONTEXT_PREFIX_PATH), ); $form[$id]['context_prefix_method_'. $id .'_key'] = array( '#title' => t('Key'), '#type' => 'textfield', '#size' => 12, '#default_value' => variable_get('context_prefix_method_'. $id .'_key', ''), ); } $form['context_prefix_location'] = array( '#type' => 'fieldset', '#title' => t('Prefix location settings'), ); $form['context_prefix_location']['context_prefix_base_domain'] = array( '#type' => 'textfield', '#title' => t('Select base domain'), '#description' => t('This setting determines the base domain for domain based context prefixing.'), '#required' => FALSE, '#default_value' => variable_get('context_prefix_base_domain', $base_url), ); $form = system_settings_form($form); $form['#theme'] = 'context_prefix_settings_form'; return $form; } /** * Theme function for context_prefix_settings_form() */ function theme_context_prefix_settings_form($form) { $output = ''; $rows = array(); foreach (element_children($form) as $id) { $row = array(); if (isset($form[$id]['#provider'])) { $name = $form[$id]['#title']; $description = $form[$id]['#description']; unset($form[$id]['#title']); unset($form[$id]['#description']); $row[] = "$name
$description
"; foreach (element_children($form[$id]) as $item) { unset($form[$id][$item]['#title']); $row[] = drupal_render($form[$id][$item]); } } $rows[] = $row; } $output .= theme('table', array(t('Provider'), t('Prefix method'), t('Key')), $rows); $output .= drupal_render($form); drupal_add_js(drupal_get_path("module", "context_prefix") ."/context_prefix_admin.js"); return $output; } /** * API Functions ====================================================== */ /** * Provides a simple API for validating, adding, and deleting context defintions. */ function context_prefix_api($op = 'insert', $context) { switch ($op) { case 'load': if (isset($context['provider'])) { if ($context['id']) { $context = db_fetch_array(db_query("SELECT * FROM {context_prefix} WHERE id = '%s' AND provider = '%s'", $context['id'], $context['provider'])); if ($context) { return $context; } } else if ($context['prefix']) { $context = db_fetch_array(db_query("SELECT * FROM {context_prefix} WHERE prefix = '%s' AND provider = '%s'", $context['prefix'], $context['provider'])); if ($context) { return $context; } } return false; } break; case 'validate': if (check_plain($context['provider']) && preg_match('!^[a-z0-9_-]+$!', $context['prefix'])) { $id = db_result(db_query("SELECT id FROM {context_prefix} WHERE prefix = '%s'", $context['prefix'])); if ($id && ($id == $context['id'])) { return true; } else if (!$id) { return true; } return false; } else { return false; } case 'insert': if (context_prefix_api('validate', $context)) { $status = db_query("INSERT INTO {context_prefix} (provider, prefix, id) VALUES ('%s', '%s', %d)", $context['provider'], $context['prefix'], $context['id']); return $status; } return false; case 'update': if (context_prefix_api('validate', $context)) { $status = db_query("UPDATE {context_prefix} SET prefix = '%s' WHERE id = '%s' AND provider = '%s'", $context['prefix'], $context['id'], $context['provider']); return $status; } case 'delete': if ($context['prefix']) { $param = 'prefix'; $where = $context['prefix']; } else if ($context['id']) { $param = 'id'; $where = $context['id']; } $check = db_result(db_query("SELECT id FROM {context_prefix} WHERE provider = '%s' AND $param = '%s'", $context['provider'], $where)); if ($check) { $status = db_query("DELETE FROM {context_prefix} WHERE provider = '%s' AND $param = '%s'", $context['provider'], $where); return $status; } return false; } return false; } /** * A wrapper around drupal_goto() that abstracts out the prefix/context setting * You provide both a normal drupal path ('node/43') and a context prefix ('dsi') * and context_prefix_goto will determine the correct location to use. */ function context_prefix_goto($provider, $id, $path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { /** * TODO: we need to abstract this base_url dissection into a * handler, and in there, we'll abstract out for * protocol handling, and handling the site's base_url like www. * * TODO: we need to make sure that other prefixed contexts don't * get dropped. e.g. if you are on 'en/node/43' and you * context_prefix_goto('spaces', 'mygroup'), you should end up * at 'en/mygroup', not 'mygroup' */ $method = variable_get('context_prefix_method_'. $provider, CONTEXT_PREFIX_PATH); $prefixes = context_prefix_prefixes($method); switch ($method) { case CONTEXT_PREFIX_PATH: foreach ($prefixes as $key => $info) { if ($info['id'] == $id) { clswitch('set', true); // Drop prefixing for context_prefix goto's $path = $key .'/'. $path; break; } } break; case CONTEXT_PREFIX_PAIR: clswitch('set', true); $prefixes = array_keys($prefixes); $path = $prefixes[0] .'/'. $id .'/'. $path; break; case CONTEXT_PREFIX_DOMAIN: foreach ($prefixes as $key => $info) { if ($info['id'] == $id) { $path = 'http://'. $key .'/'. $path; break; } } break; } drupal_goto($path, $query, $fragment, $http_response_code); } /** * Custom l wrapper for links that need to leave all group contexts * * TODO: Like context_prefix_goto(), this function needs to allow the * selective dropping/adding of contexts. */ function cl($text, $path, $attributes = array(), $query = NULL, $fragment = NULL, $absolute = FALSE, $html = FALSE, $dropcontext = FALSE) { global $base_url; clswitch('set', $dropcontext); if (!$dropcontext && $path == '') { $path = context_prefix_url_rewrite('alias', '', ''); } // Handle domains -- need to force domain onto the path and push through as absolute url if ($dropcontext) { $absolute = TRUE; if ($path == '') { $path = variable_get('site_frontpage', 'node'); } if ($domain = variable_get('context_prefix_base_domain', '')) { $path = $domain .'/'. $path; // REPLACE BASE_URL with the hub domain. } } $l = l($text, $path, $attributes, $query, $fragment, $absolute, $html); clswitch('reset'); return $l; } /** * Returns whether the current l/url call should use context rewriting or not */ function clswitch($op, $absolute = null) { static $drop; switch ($op) { case 'set'; $drop = $absolute; break; case 'get': return $drop; break; case 'reset': $drop = null; break; } } /** * Like theme_links, but handles context warping. * theme_links couldn't believe it. */ function theme_context_links($links, $attributes = array('class' => 'links')) { $output = ''; if (count($links) > 0) { $output = ''; $num_links = count($links); $i = 1; foreach ($links as $key => $link) { $class = ''; // Automatically add a class to each link and also to each LI if (isset($link['attributes']) && isset($link['attributes']['class'])) { $link['attributes']['class'] .= ' '. $key; $class = $key; } else { $link['attributes']['class'] = $key; $class = $key; } // Add active class for active menu items if (stristr($key, 'active')) { $class .= " active"; } // Add first and last classes to the list of links to help out themers. $extra_class = ''; if ($i == 1) { $extra_class .= 'first '; } if ($i == $num_links) { $extra_class .= 'last '; } $output .= '
  • '; // Is the title HTML? $html = isset($link['html']) && $link['html']; // Initialize fragment and query variables. $link['query'] = isset($link['query']) ? $link['query'] : NULL; $link['fragment'] = isset($link['fragment']) ? $link['fragment'] : NULL; if (isset($link['href'])) { if ($link['warp']) { $output .= cl($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html, TRUE); } else { $output .= l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html); } } else if ($link['title']) { //Some links are actually not links, but we wrap these in for adding title and class attributes if (!$html) { $link['title'] = check_plain($link['title']); } $output .= ''. $link['title'] .''; } $i++; $output .= "
  • \n"; } $output .= ''; } return $output; } /** * Generates a context prefix form element that can be dropped into a * FormAPI form array. Includes validation, but nsert/update must be * handled by the implementing submit handler. */ function context_prefix_form($provider, $id, $prefix = '') { switch (variable_get('context_prefix_method_'. $provider, CONTEXT_PREFIX_PATH)) { case CONTEXT_PREFIX_PATH: case CONTEXT_PREFIX_PAIR: $description = t('Choose a prefix path. May contain only lowercase letters, numbers, dashes and underscores. e.g. "my-prefix"'); break; case CONTEXT_PREFIX_SUBDOMAIN: $description = t('Enter a domain registered for this context, such as "mygroup". Do not include http://'); break; case CONTEXT_PREFIX_DOMAIN: $description = t('Enter a domain registered for this context, such as "www.example.com". Do not include http://'); break; } $form = array( '#tree' => TRUE, '#validate' => array('context_prefix_form_validate' => array()), ); $form['prefix'] = array( '#title' => t('Path prefix'), '#type' => 'textfield', '#description' => $description, '#maxlength' => 255, '#required' => true, '#default_value' => $prefix, ); $form['provider'] = array( '#type' => 'value', '#value' => $provider, ); $form['id'] = array( '#type' => 'value', '#value' => $id, ); return $form; } /** * Validation handler for context_prefix_form(). */ function context_prefix_form_validate($form) { $definition = array( 'provider' => $form['provider']['#value'], 'prefix' => $form['prefix']['#value'], 'id' => $form['id']['#value'], ); if (!context_prefix_api('validate', $definition)) { form_set_error($form['#parents'][0], t('There was an error registering the prefix "@prefix". It is either invalid or is already taken. Please choose another.', array('@prefix' => $form['prefix']['#value']))); return false; } else { return true; } } /** * Specialized cache for storing prefix information. */ class context_prefix_cache { protected $cache = array(); function __construct() { $this->cache[CONTEXT_PREFIX_PATH] = array(); $this->cache[CONTEXT_PREFIX_PAIR] = array(); $this->cache[CONTEXT_PREFIX_SUBDOMAIN] = array(); $this->cache[CONTEXT_PREFIX_DOMAIN] = array(); } /** * @param $method * The method to add to the cache for * @param $item * Either a integer|string, or keyed array to add * @param $merge * Preserve keys and merge into cache for method. */ public function add($method, $item, $merge = true) { if (is_array($item) && $merge) { // Need to preserve keys so we use the '+' array operator. $this->cache[$method] = $this->cache[$method] + $item; } else { $this->cache[$method][] = $item; } } /** * @param $method * The method to retrieve from the cache for. * @param $item * Optionally and key of the required info. * * @return the desired info or false if an id doesn't exist. */ public function get($method = false, $id = false) { if ($method !== false && $id !== false) { return (isset($this->cache[$method][$id]) ? $this->cache[$method][$id] : false); } elseif ($method !== false) { return $this->cache[$method]; } else { return $this->cache; } } } /** * Helper function, returns form options for prefix types. */ function _context_ui_options() { return array( CONTEXT_PREFIX_PATH => t('Path'), CONTEXT_PREFIX_PAIR => t('Keyed pair'), CONTEXT_PREFIX_DOMAIN => t('Full domain'), // CONTEXT_PREFIX_SUBDOMAIN => t('Subdomain'), ); }