Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.267
diff -u -p -r1.267 bootstrap.inc
--- includes/bootstrap.inc	22 Jan 2009 03:05:17 -0000	1.267
+++ includes/bootstrap.inc	25 Jan 2009 02:33:32 -0000
@@ -1615,3 +1615,82 @@ function registry_rebuild() {
 /**
  * @} End of "ingroup registry".
  */
+
+/**
+ * Returns the global environment variable.
+ *
+ * Technically this is a singleton, which is bad, but we have to break out
+ * of the dependency injection loop sometime.
+ *
+ * @ingroup environment
+ * @param $reset
+ *   Set TRUE to force the environment variable to be regenerated.
+ * @return
+ *   A Drupal environment object.
+ */
+function env($reset = FALSE) {
+  static $env;
+
+  if (empty($env) || $reset) {
+  $env = new EnvironmentDefault();
+  }
+  return $env;
+}
+
+/**
+ * @ingroup handlers
+ * @{
+ */
+
+/**
+ * Main handler entry-point.
+ *
+ * This function should be called in most instances in place of a factory
+ * function.  It will automatically route the request to the appropriate
+ * factory.
+ *
+ * @param $slot_id
+ *   The internal ID of the slot for which we want to retrieve a handler.
+ * @param $target
+ *   The target for which we want the attached handler.
+ * @return
+ *   The associated handler object.
+ */
+function handler($slot_id, $target = 'default') {
+  require_once DRUPAL_ROOT . '/includes/handlers.inc';
+  $slot = slot_load($slot_id);
+  if ($slot) {
+    $function = $slot->factory;
+    if (drupal_function_exists($function)) {
+      return $function($target, $slot_id);
+    }
+  }
+
+  return NULL;
+}
+
+/**
+ * Interface for all Handler classes for any slot.
+ *
+ * This is a very thin interface, but does serve to help standardize handler
+ * behavior, especially interaction with an environment object.  Interfaces
+ * for specific slots should extend this interface.
+ */
+interface HandlerInterface {
+
+  /**
+   * Constructor
+   *
+   * @param $env
+   *   The environment object this handler should use for interacting with
+   *   the rest of Drupal.
+   * @param $target
+   *   The target for which this handler is being called. Some handlers may
+   *   require this information in order to route commands properly.
+   */
+  function __construct(DrupalEnvironmentInterface $env, $target = 'default');
+}
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.856
diff -u -p -r1.856 common.inc
--- includes/common.inc	23 Jan 2009 14:23:27 -0000	1.856
+++ includes/common.inc	25 Jan 2009 02:33:32 -0000
@@ -4037,6 +4037,7 @@ function drupal_flush_all_caches() {
   _drupal_flush_css_js();
 
   registry_rebuild();
+  handlers_rebuild();
   drupal_clear_css_cache();
   drupal_clear_js_cache();
 
@@ -4079,3 +4080,70 @@ function _drupal_flush_css_js() {
   }
   variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
 }
+
+/**
+ * @ingroup handlers
+ * @{
+ */
+
+/**
+ * Rebuild the handler and slot registries.
+ */
+function handlers_rebuild() {
+  require_once DRUPAL_ROOT . '/includes/handlers.inc';
+  handler_slot_build();
+  handler_handler_build();
+}
+
+/**
+ * Asociate a given handler to a give slot for the specified target.
+ *
+ * @param $slot_id
+ *   The internal ID of the slot.
+ * @param $handler_id
+ *   The internal ID of the hand
+ * @param $target
+ *   The target for which to associate this handler to this slot.
+ */
+function handler_attach($slot_id, $handler_id, $target = 'default') {
+
+  // Lazy-load the utility function.
+  drupal_function_exists('handler_load');
+
+  $handler = handler_load($slot_id, $handler_id);
+
+  if ($handler) {
+    // We store the class in the database rather than the handler ID so that
+    // we can, on lookup, get back the class to instantiate and immediately
+    // do so.
+    db_merge('handler_attachments')
+     ->key(array(
+       'slot' => $slot_id,
+       'target' => $target,
+     ))
+     ->fields(array(
+       'class' => $handler->class,
+     ))
+     ->execute();
+  }
+}
+
+/**
+ * Revert a given slot and target to the default handler.
+ *
+ * @param $slot_id
+ *   The slot to reset to the default handler.
+ * @param $target
+ *   The target to reset to the default handler.
+ */
+function handler_detach($slot_id, $target = 'default') {
+  db_delete('handler_attachments')
+    ->condition('slot', $slot_id)
+    ->condition('target', $target)
+    ->execute();
+}
+
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: includes/environment-default.inc
===================================================================
RCS file: includes/environment-default.inc
diff -N includes/environment-default.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/environment-default.inc	25 Jan 2009 02:33:32 -0000
@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * @file
+ * The default Drupal environment object.
+ */
+
+/**
+ * @defgroup environment Drupal environment wrappers
+ * @{
+ * The "Drupal environment" is the sum total of global state about a Drupal
+ * page request.  That includes information stored in PHP's superglobals,
+ * Drupal-specific derived information such as the request path and arguments,
+ * and Drupal state such as variables.
+ *
+ * The purpose of the environment system is to provide an encapsulated single
+ * point of access for that data.  By providing a single point for it, we also
+ * provide a single point to override it.  That is especially useful for unit
+ * testing, where code that relies solely on an environment object can be passed
+ * a "mock" environment object representing a fake environment we want to test.
+ * For example, we could test how a certain piece of code behaves when certain
+ * HTTP POST data without actually issuing a POST request at all.  We simply
+ * simulate the POST request as a new environment object, pass that to the code
+ * to be tested, and see how it behaves.
+ *
+ * In the vast majority of cases on a live site there will be only a single
+ * environment object.  For Handlers and any other code that has the
+ * environment object passed to it (a pattern known as "Dependency Injection"),
+ * it should only assume that the object conforms to the DrupalEnvironmentInterface
+ * interface.  Any environment-related requests should then be routed through
+ * the environment object rather than using the procedural function equivalents.
+ *
+ *  For example:
+ *  @code
+ *  class MyClass {
+ *
+ *    protected $env;
+ *    protected $string = '';
+ *
+ *    function __construct(DrupalEnvironmentInterface $env) {
+ *      $this->env = $env;
+ *    }
+ *
+ *    public function otherMethod($string) {
+ *      // Incorrect
+ *      $var = variable_get('myvar', 'default');
+ *
+ *      // Correct
+ *      $var = $this->env->variableGet('myvar', 'default');
+ *
+ *      // Incorrect
+ *      $param = isset($_GET['page']) ? $_GET['page'] : 0;
+ *
+ *      // Correct
+ *      $param = $this->env->httpGet('page', 0);
+ *
+ *      // Incorrect
+ *      $result = db_query("SELECT title FROM {node} WHERE nid= :nid", array(':nid' => 5));
+ *
+ *      // Correct
+ *      $result = $this->env->db()->query("SELECT title FROM {node} WHERE nid= :nid", array(':nid' => 5));
+ *    }
+ *  }
+ *  @endcode
+ *
+ * If it is necessary to actively request the environment object, do not instantiate
+ * a new one.  Instead, use the env() factory function.
+ *
+ */
+
+/**
+ * The basic Drupal environment object interface.
+ */
+interface DrupalEnvironmentInterface {
+
+  /**
+   * Return a persistent variable.
+   *
+   * @param $name
+   *   The name of the variable to return.
+   * @param $default
+   *   The default value to use if this variable has never been set.
+   * @return
+   *   The value of the variable.
+   */
+  public function variableGet($var, $default);
+
+  /**
+   * Set a persistent variable.
+   *
+   * @param $name
+   *   The name of the variable to set.
+   * @param $value
+   *   The value to set. This can be any PHP data type; these routines take care
+   *   of serialization as necessary.
+   */
+  public function variableSet($var, $default);
+
+  /**
+   * Return a variable from the HTTP GET directive.
+   *
+   * @see http://www.php.net/manual/en/reserved.variables.get.php
+   * @param $key
+   *   The value from the GET query to return.  If not specified, the entire
+   *   array will be returned.
+   * @param $default
+   *   The default value to use if the specified key was not sent.
+   * @return
+   *   The value from the GET array.
+   */
+  public function httpGet($key = NULL, $default = NULL);
+
+  /**
+   * Return a variable from the HTTP POST directive.
+   *
+   * @see http://www.php.net/manual/en/reserved.variables.post.php
+   * @param $key
+   *   The value from the POST query to return.  If not specified, the entire
+   *   array will be returned.
+   * @param $default
+   *   The default value to use if the specified key was not sent.
+   * @return
+   *   The value from the POST array.
+   */
+  public function httpPost($key = NULL, $default = NULL);
+
+  /**
+   * Return a variable from HTTP cookies.
+   *
+   * @see http://www.php.net/manual/en/reserved.variables.cookie.php
+   * @param $key
+   *   The cookie name to return.  If not specified, the entire
+   *   array will be returned.
+   * @param $default
+   *   The default value to use if the specified key was not sent.
+   * @return
+   *   The value from the COOKIE array.
+   */
+  public function httpCookie($key = NULL, $default = NULL);
+
+  /**
+   * Return a single argument from the menu query string.
+   * @param $arg
+   *   The 0-based index of the argument to return.
+   * @return
+   *   The argument element as a string, or NULL if it was not provided.
+   */
+  public function requestArgument($arg);
+
+  /**
+   * Return the entire original menu query string.
+   *
+   * @return
+   *   The value originally in $_GET['q'].
+   */
+  public function requestPath();
+
+  /**
+   * Return a property about the server and request configuration.
+   *
+   * This is a wrapper for the $_SERVER superglobal.
+   *
+   * @see http://www.php.net/manual/en/reserved.variables.server.php
+   * @param $key
+   *   The value from the $_SERVER superglobal.  If not specified, the entire
+   *   array will be returned.
+   * @param $default
+   *   The default value to use if the specified key was not sent.
+   * @return
+   *   The value from the SERVER array.
+   */
+  public function serverInfo($key = NULL, $default = NULL);
+
+  /**
+   * Return a property about the server and request configuration.
+   *
+   * This is a wrapper for the $_ENV superglobal.
+   *
+   * @see http://www.php.net/manual/en/reserved.variables.environment.php
+   * @param $key
+   *   The value from the $_ENV superglobal.  If not specified, the entire
+   *   array will be returned.
+   * @param $default
+   *   The default value to use if the specified key was not sent.
+   * @return
+   *   The value from the ENV array.
+   */
+  public function envInfo($key = NULL, $default = NULL);
+
+  /**
+   * Invoke a hook in all enabled modules that implement it.
+   *
+   * @param $hook
+   *   The name of the hook to invoke.
+   * @param ...
+   *   Arguments to pass to the hook.
+   * @return
+   *   An array of return values of the hook implementations. If modules return
+   *   arrays from their implementations, those are merged into one array.
+   */
+  public function invokeHooks($hook);
+
+  /**
+   * Request a handler object.
+   *
+   * @param $slot
+   *   The slot for which we want a handler.
+   * @param $options
+   *   An array of options to help determine which handler we want.
+   * @return
+   *   The appropriate handler object.
+   */
+  public function handler($slot, $options = array());
+
+  /**
+   * Gets the connection object for the specified database key and target.
+   *
+   * In most cases, calling this method with no parameters is sufficient,
+   * as that will return a connection object for the active database, default
+   * target (master server).
+   *
+   * @param $target
+   *   The database target to request.  Default is "default".
+   * @param $key
+   *   The key of the connection to reuqest.  If not specified,
+   *   uses whatever the currently active database is.
+   * @return
+   *   The corresponding connection object.
+   */
+  public function db($target = 'default', $key = NULL);
+}
+
+/**
+ * The default Drupal environment object.
+ *
+ * This is the default implementation, intended for "live" usage.  In the vast
+ * majority of cases this is the only version that will be needed for
+ * production sites.  However, unit tests can provide alternate implementations
+ * as needed.
+ */
+class EnvironmentDefault implements DrupalEnvironmentInterface {
+
+  protected $requestPath;
+  protected $getArray;
+  protected $postArray;
+  protected $cookieArray;
+  protected $serverArray;
+  protected $envArray;
+
+  public function __construct() {
+    $this->requestPath = drupal_get_normal_path($_GET['q']);
+
+    $this->getArray = $_GET;
+    $this->postArray = $_POST;
+    $this->cookieArray = $_COOKIE;
+    $this->serverArray = $_SERVER;
+    $this->envArray = $_ENV;
+    unset($this->getArray['q']);
+
+    $this->requestArguments = explode('/', $this->requestPath);
+
+  }
+
+  public function variableGet($var, $default) {
+    return variable_get($var, $default);
+  }
+
+  public function variableSet($var, $default) {
+    return variable_set($var, $default);
+  }
+
+  public function requestPath() {
+    return $this->requestPath;
+  }
+
+  protected function arrayIndexLookup($array, $key = NULL, $default = NULL) {
+    if (isset($key)) {
+      return isset($this->$array[$key]) ? $this->$array[$key] : $default;
+    }
+    else {
+      return $this->$array;
+    }
+  }
+
+  public function httpGet($key = NULL, $default = NULL) {
+    return $this->arrayIndexLookup('getArray', $key, $default);
+  }
+
+  public function httpPost($key = NULL, $default = NULL) {
+    return $this->arrayIndexLookup('postArray', $key, $default);
+  }
+
+  public function httpCookie($key = NULL, $default = NULL) {
+    return $this->arrayIndexLookup('cookieArray', $key, $default);
+  }
+
+  public function serverInfo($key = NULL, $default = NULL) {
+    return $this->arrayIndexLookup('serverArray', $key, $default);
+  }
+
+  public function envInfo($key = NULL, $default = NULL) {
+    return $this->arrayIndexLookup('envArray', $key, $default);
+  }
+
+  public function requestArgument($arg) {
+    return isset($this->requestArguments[$arg]) ? $this->requestArguments[$arg] : NULL;
+  }
+
+  public function invokeHooks($hook) {
+    return call_user_func_array('module_invoke_all', func_get_args());
+  }
+
+  public function handler($slot, $target = 'default') {
+    return handler($slot, $target);
+  }
+
+  public function db($target = 'default', $key = NULL) {
+  	if (empty($key)) {
+  		$key = 'default';
+  	}
+  	return Database::getConnection($key, $target);
+  }
+
+}
+
+/**
+ * @} End of "ingroup environment".
+ */
Index: includes/handlers.inc
===================================================================
RCS file: includes/handlers.inc
diff -N includes/handlers.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/handlers.inc	25 Jan 2009 02:33:32 -0000
@@ -0,0 +1,291 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Handler utility functions.
+ *
+ * These functions are only needed when actually triggering a handler, so don't
+ * include them unless needed.
+ */
+
+/**
+ * @defgroup handlers Handlers system
+ * @{
+ * Handlers are a standardized mechanism for swappable subsystems.  Whereas
+ * hooks are useful for allowing any and all other modules to respond to or
+ * modify some event, Handlers are a way for code to delegate certain behavior
+ * to an easily swappable object.  That could be anything from a simple string
+ * operation up through an entire rendering engine.
+ *
+ * The Handlers system includes a number of key parts:
+ *
+ * Slot
+ * A slot is a system or subsystem which we want to be easily swappable. A slot
+ * is defined by a name and an interface. All handlers for a given slot must
+ * conform to the PHP interface defined by the slot.
+ *
+ * Handler
+ * A Handler is a class that implements the interface for a given slot. That is,
+ * a Cache Handler is one particular implementation of the cache system and can
+ * be transparently swapped out for another Cache Handler. Handlers are by
+ * design limited in their communication to the rest of the system to an
+ * environment object.
+ *
+ * Targets
+ * A target is a specific case within a slot. Every slot may have an unlimited
+ * number of arbitrarily defined targets, and every target may have a different
+ * handler attached to it.
+ *
+ * Slots and Handlers are defined by hook_slot_info and hook_handler_info(),
+ * respectively. Once defined, a handler may be "attached" to a specific target
+ * like so:
+ *
+ * @code
+ * handler_attach('slot_name', 'handler_name', 'target');
+ * @endcode
+ *
+ * That will overwrite any previously associated handler. A handler may then
+ * be requested using the handler() factory function:
+ *
+ * @code
+ * $handler = handler('slot_name', 'target');
+ * $handler->someMethod();
+ * @endcode
+ *
+ * If no handler has been attached to that slot and target, the default handler
+ * specified by the slot definition is used instead.  A slot/target may also be
+ * "reset" to the default handler:
+ *
+ * @code
+ * handler_detach('slot_name', 'target');
+ * @endcode
+ *
+ * If no target is specified, "default" is assumed.  That allows for a given
+ * slot to not make use of the system's multi-target capabilities by simply
+ * omitting the target whenever it is called, which results in a target of
+ * "default" being used in all cases.
+ */
+
+/**
+ * Rebuild the handler slot info registry.
+ *
+ * @return
+ *   The array of slots just declared.
+ */
+function handler_slot_build() {
+
+  $slots = module_invoke_all('slot_info');
+
+  // Set default values, to avoid NULL issues if nothing else.
+  foreach ($slots as $slot => $info) {
+    $slots[$slot] += handler_slot_defaults();
+  }
+
+  // Let other modules alter the handler target registry if needed.
+  drupal_alter('slot_info', $slots);
+
+  // Build the target data.
+  $insert = db_insert('handler_slot_info')->fields(array('slot', 'title', 'interface', 'factory', 'default_handler', 'description'));
+
+  foreach ($slots as $slot => $info) {
+  $insert->values(array(
+   'slot' => $slot,
+   'title' => $info['title'],
+   'interface' => $info['interface'],
+   'factory' => $info['factory'],
+   'default_handler' => $info['default_handler'],
+   'description' => $info['description'],
+  ));
+  }
+
+  // Don't do the deletion until after we've gotten the new data prepared.  That
+  // reduces the potential race condition window.
+  db_delete('handler_slot_info')->execute();
+
+  $insert->execute();
+
+  return $slots;
+}
+
+/**
+ * Rebuild the handler info registry.
+ *
+ * @return
+ *   The array of handlers just defined.
+ */
+function handler_handler_build() {
+
+  $handlers = module_invoke_all('handler_info');
+
+  // Set default values, to avoid NULL issues if nothing else.
+  foreach ($handlers as $slot => $handler_info) {
+    foreach ($handler_info as $handler => $info) {
+      $handlers[$slot][$handler] += handler_handler_defaults();
+    }
+  }
+
+  // Let other modules alter the handler registry if needed.
+  drupal_alter('handler_info', $handlers);
+
+  // Build the handler data.
+  $insert = db_insert('handler_info')->fields(array('handler', 'slot', 'title', 'class', 'description'));
+  foreach ($handlers as $slot => $handler_info) {
+    foreach ($handler_info as $handler => $info) {
+      $insert->values(array(
+        'handler' => $handler,
+      'slot' => $slot,
+      'title' => $info['title'],
+      'class' => $info['class'],
+      'description' => $info['description'],
+      ));
+    }
+  }
+
+  // Don't do the deletion until after we've gotten the new data.  That reduces
+  // the potential race condition window.
+  db_delete('handler_info')->execute();
+
+  $insert->execute();
+
+  return $handlers ;
+}
+
+/**
+ * Define various default values for handler slots.
+ *
+ * @return
+ *   The default "empty" slot definition.
+ */
+function handler_slot_defaults() {
+  return array(
+    'slot' => '',
+    'title' => '',
+    'interface' => 'HandlerInterface',
+    'factory' => 'handler_factory_default',
+    'default_handler' => 'default',
+    'description' => '',
+  );
+}
+
+/**
+ * Define various default values for handlers.
+ *
+ * @return
+ *   The default "empty" handler definition.
+ */
+function handler_handler_defaults() {
+  return array(
+    'handler' => '',
+    'slot' => '',
+    'title' => '',
+    'class' => '',
+    'description' => '',
+  );
+}
+
+/**
+ * Load function for slot info objects.
+ *
+ * @param $slot_id
+ *   The internal slot ID.
+ * @param $refresh
+ *   If this is set to TRUE, the internal slot cache will be flushed.
+ * @return
+ *   The slot object or NULL if it is not defined.
+ */
+function slot_load($slot_id, $refresh = FALSE) {
+  static $slots;
+
+  if ($refresh) {
+    $slots = array();
+  }
+
+  if (empty($slots[$slot_id])) {
+    $slots[$slot_id] = db_query("SELECT slot, title, interface, factory, default_handler, description FROM {handler_slot_info} WHERE slot = :slot_id", array(':slot_id' => $slot_id))->fetchObject();
+  }
+
+  return $slots[$slot_id];
+}
+
+/**
+ * Load function for handler info objects.
+ *
+ * @param $slot_id
+ *   The internal slot ID.  All handlers are namespaced within their slot.
+ * @param $handler_id
+ *   The internal handler ID.
+ * @param $refresh
+ *   If this is set to TRUE, the internal handler cache will be flushed.
+ * @return
+ *   The handler object or NULL if not defined.
+ */
+function handler_load($slot_id, $handler_id, $refresh = FALSE) {
+  static $handlers;
+
+  if ($refresh) {
+    $handlers = array();
+  }
+
+  if (empty($handlers[$slot_id][$handler_id])) {
+    $handlers[$slot_id][$handler_id] = db_query("SELECT handler, slot, title, class, description FROM {handler_info} WHERE slot = :slot_id AND handler = :handler_id", array(
+      ':slot_id' => $slot_id,
+      ':handler_id' => $handler_id,
+    ))->fetchObject();
+  }
+
+  return $handlers[$slot_id][$handler_id];
+}
+
+/**
+ * The generic handler factory.
+ *
+ * In most cases, this factory will be sufficient for handlers that do not
+ * need a new object generated on each call.
+ *
+ * The default factory function will statically cache handlers so that the same
+ * object is returned for each call to the same slot/target pair.
+ *
+ * The $target and $slot_id parameters are in reverse order so that alternate
+ * factory functions for specific slots can ignore the $slot_id (which they
+ * will already know) and still get the $target, which is always important.
+ *
+ * @param $target
+ *   The target determines which handler object should be used, depending
+ *   on the current configuration.
+ * @param $slot_id
+ *   The slot we are handling.
+ * @return
+ *   The handler object required.
+ */
+function handler_factory_default($target, $slot_id) {
+  static $handlers;
+
+  if (empty($handlers[$slot_id][$target])) {
+
+  // Try to get the associated handler.
+    $class = db_query("SELECT class FROM {handler_attachments} WHERE slot = :slot AND target = :target", array(
+      'slot' => $slot_id,
+      ':target' => $target,
+    ))->fetchField();
+
+    // If NULL was returned, there is no handler associated for that target.
+    // Instead, use the default handler's class.  If that fails too, then
+    // the slot is improperly defined so we don't care.
+    if (!$class) {
+      $class = db_query("SELECT class
+        FROM {handler_info} h
+          INNER JOIN {handler_slot_info} s
+            ON h.handler = s.default_handler
+        WHERE h.slot = :slot", array(':slot' => $slot_id))->fetchField();
+    }
+
+    $handlers[$slot_id][$target] = new $class(env(), $target);
+  }
+
+  return $handlers[$slot_id][$target];
+}
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: modules/simpletest/tests/environment.test
===================================================================
RCS file: modules/simpletest/tests/environment.test
diff -N modules/simpletest/tests/environment.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/environment.test	25 Jan 2009 02:33:32 -0000
@@ -0,0 +1,65 @@
+<?php
+// $Id$
+
+/**
+ * Tests for the environment object.
+ */
+class EnvironmentTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Default environment'),
+      'description' => t('Test the default environment object'),
+      'group' => t('Handlers'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // To unit test alternate implementations, subclass this test class,
+    // override this name, and run it.  That applies the same tests to any
+    // implementation.
+    $this->environmentName = 'EnvironmentDefault';
+
+  }
+
+  /**
+   * Test that the env() utility function works.
+   */
+  function testEnv() {
+
+  	$env1 = env();
+  	$env2 = env();
+
+  	$env3 = env(TRUE);
+
+  	$this->assertTrue($env1 instanceof DrupalEnvironmentInterface, t('First returned object has correct interface.'));
+  	$this->assertTrue($env2 instanceof DrupalEnvironmentInterface, t('Second returned object has correct interface.'));
+  	$this->assertTrue($env3 instanceof DrupalEnvironmentInterface, t('Third returned object has correct interface.'));
+
+  	$this->assertIdentical($env1, $env2, t('First two requests returned the same environment object.'));
+    $this->assertNotIdentical($env1, $env3, t('Reset parameter worked correctly.'));
+  }
+
+  /**
+   * Test that we can access page arguments properly.
+   */
+  function testArguments() {
+
+    $env = new $this->environmentName;
+
+    $this->assertEqual($env->requestArgument(0), arg(0), t('Correct argument returned.'));
+  }
+
+  /**
+   * Test that we can connect to the database through the environment object.
+   */
+  function testDatabase() {
+
+    $env = new $this->environmentName;
+
+    $this->assertIdentical($env->db(), Database::getConnection('default', 'default'), t('Correct database connection retrieved.'));
+  }
+}
+
Index: modules/simpletest/tests/handlers.test
===================================================================
RCS file: modules/simpletest/tests/handlers.test
diff -N modules/simpletest/tests/handlers.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers.test	25 Jan 2009 02:33:32 -0000
@@ -0,0 +1,198 @@
+<?php
+// $Id$
+
+/**
+ * Alternate environment object for testing the sample handler.
+ */
+class EnvironmentVariableOverride extends EnvironmentDefault {
+
+protected $overrides = array();
+
+/**
+ * Override variableGet() to control the variable table.
+ *
+ * @see includes/EnvironmentDefault#variableGet()
+ */
+  public function variableGet($var, $default) {
+
+  if (!empty($this->overrides[$var])) {
+    return $this->overrides[$var];
+  }
+
+    return variable_get($var, $default);
+  }
+
+  /**
+   * Override variableSet to control the variable table.
+   *
+   * @see includes/EnvironmentDefault#variableSet()
+   */
+  public function variableSet($var, $default) {
+    $this->overrides[$var] = $default;
+  }
+}
+
+/**
+ * Test class for the Handlers system.
+ */
+class HandlersTestCase extends DrupalWebTestCase {
+
+  protected $sampleString = 'Hello World';
+
+  function getInfo() {
+    return array(
+      'name' => t('Handlers'),
+      'description' => t('Test the Handler routing system'),
+      'group' => t('Handlers'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('handlers_test');
+
+    // We need to rebuild the index at least once, because the module_enable()
+    // routine called by the parent method doesn't call drupal_flush_all_caches().
+    handlers_rebuild();
+  }
+
+  /**
+   * Test that we can retrieve the default handler if no target is defined.
+   */
+  function testDefaultHandler() {
+    // 'default' has not been attached to anything, so should return the
+    // default implementation.
+    $handler = handler('fancystring', 'default');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Default handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test that we can specify a handler for a target and it loads correctly.
+   */
+  function testSpecifiedHandler() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'rot13', 'foo');
+
+    // Now request the handler.
+    $handler = handler('fancystring', 'foo');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringRot13, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual(str_rot13($this->sampleString), $mangled, t('Specified handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test that we can skip using target on a slot and everything still works.
+   */
+  function testUndefinedHandler() {
+    // An undefined target should always give us the default handler defined by the slot.
+    $handler = handler('fancystring');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Default handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test a specific handler using a mocked environment object.
+   *
+   * This doesn't test the handler routing system, but does demonstrate how
+   * the environment object is useful for testing.
+   */
+  function testMockEnvironmentHandler() {
+
+    $map = array(
+      'Hello' => 'Goodbye',
+      'World' => 'Cruel World',
+    );
+
+    $env = new EnvironmentVariableOverride();
+    $env->variableSet('fancystring_translate', $map);
+
+    $handler = new FancystringCustom($env);
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringCustom, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual(strtr($this->sampleString, $map), $mangled, t('Specified handler returned correct string.'));
+    }
+  }
+}
+
+/**
+ * Test class for the Handlers system, part 2.
+ */
+class Handlers2TestCase extends DrupalWebTestCase {
+
+  protected $sampleString = 'Hello World';
+
+  function getInfo() {
+    return array(
+      'name' => t('Handlers 2'),
+      'description' => t('Test the Handler routing system, part 2'),
+      'group' => t('Handlers'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('handlers_test');
+
+    // We need to rebuild the index at least once, because the module_enable()
+    // routine called by the parent method doesn't call drupal_flush_all_caches().
+    handlers_rebuild();
+  }
+
+  /**
+   * Test that we can successfully unset a handler attachment.
+   *
+   * We have to break this to a separate unit test class to avoid issues with
+   * static caching in the default factory.
+   */
+  function testUnsettingHandler() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'rot13', 'foo');
+
+    // Now unset that attachment.
+    handler_detach('fancystring', 'foo');
+
+    // Now request the handler.  We should get back the default.
+    $handler = handler('fancystring', 'foo');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Unspecified handler returned correct string.'));
+    }
+  }
+}
+
Index: modules/simpletest/tests/handlers_test.info
===================================================================
RCS file: modules/simpletest/tests/handlers_test.info
diff -N modules/simpletest/tests/handlers_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.info	25 Jan 2009 02:33:32 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "Handlers Test"
+description = "Support module for Handlers tests."
+core = 7.x
+package = Testing
+files[] = handlers_test.module
+version = VERSION
+hidden = TRUE
Index: modules/simpletest/tests/handlers_test.module
===================================================================
RCS file: modules/simpletest/tests/handlers_test.module
diff -N modules/simpletest/tests/handlers_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.module	25 Jan 2009 02:33:33 -0000
@@ -0,0 +1,144 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_slot_info().
+ */
+function handlers_test_slot_info() {
+  return array(
+    'fancystring' => array(
+      'title' => 'Fancy string',
+      'description' => 'Do fancy stuff to strings',
+      'interface' => 'FancystringInterface',
+      'default_handler' => 'default',
+    ),
+    'other' => array(
+      'title' => 'Other handler',
+      'description' => 'Dummy',
+      'targets' => array(),
+      'interface' => 'OtherInterface',
+      //'factory' => 'other',
+    ),
+
+  );
+}
+
+/**
+ * Implementation of hook_handler_info().
+ */
+function handlers_test_handler_info() {
+  return array(
+    'fancystring' => array(
+       'default' => array(
+         // Translate on load, not define, like hook_menu().
+         'title' => 'No string mutation',
+         'class' => 'FancystringDefault',
+         'description' => "This handler doesn't do anything to the string.  It passes through unaltered.",
+       ),
+       'rot13' => array(
+         'title' => 'Rot13 translation',
+         'class' => 'FancystringRot13',
+         'description' => 'This handler ROT13 encrypts a string.',
+       ),
+       'custom_translate' => array(
+         'title' => 'Custom mapping',
+         'class' => 'FancystringCustom',
+         'description' => 'This handler uses a user-specified mapping array.',
+       ),
+     ),
+  );
+}
+
+/**
+ * Interface for the example string mangling handler.
+ */
+interface FancystringInterface extends HandlerInterface {
+
+/**
+ * Set the string for this object to mangle.
+ * @param $string
+ *   The string to mangle.
+ */
+  public function setString($string);
+
+  /**
+   * Get the string back, mangled according to this handler.
+   * @return
+   *   The mangled string.
+   */
+  public function getString();
+}
+
+/**
+ * Base implementation of Fancystring handler.
+ */
+abstract class FancystringBase implements FancystringInterface {
+
+protected $env;
+
+protected $target;
+
+  protected $string = '';
+
+  function __construct(DrupalEnvironmentInterface $env, $target = 'default') {
+    $this->env = $env;
+    $this->target = $target;
+  }
+
+  public function setString($string) {
+    $this->string = $string;
+  }
+}
+
+/**
+ * Default implementation of Fancystring handler.
+ */
+class FancystringDefault extends FancystringBase {
+
+  public function getString() {
+    return $this->string;
+  }
+}
+
+/**
+ * Rot13 implementation of Fancystring handler.
+ */
+class FancystringRot13 extends FancystringBase {
+
+  public function getString() {
+    return str_rot13($this->string);
+  }
+}
+
+/**
+ * Custom replacement implementation of Fancystring handler.
+ *
+ * Note that we are implementing the FancystringInterface here directly
+ * rather than inheriting from the base class, mostly to show that we can.
+ * There's no obligation that handlers do anything other than support the
+ * appropriate interface.  How they do so is entirely up to them.
+ *
+ */
+class FancystringCustom implements FancystringInterface {
+
+  protected $env;
+
+  protected $string = '';
+
+  protected $target;
+
+  function __construct(DrupalEnvironmentInterface $env, $target = 'default') {
+    $this->env = $env;
+    $this->target = $target;
+  }
+
+  public function setString($string) {
+    $this->string = $string;
+  }
+
+  public function getString() {
+    $map = $this->env->variableGet('fancystring_translate', array());
+
+    return strtr($this->string, $map);
+  }
+}
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.13
diff -u -p -r1.13 system.api.php
--- modules/system/system.api.php	17 Jan 2009 20:28:46 -0000	1.13
+++ modules/system/system.api.php	25 Jan 2009 02:33:33 -0000
@@ -1382,14 +1382,14 @@ function hook_schema_alter(&$schema) {
  *
  * @see hook_query_TAG_alter()
  * @see node_query_node_access_alter()
- * 
+ *
  * @param $query
  *   A Query object describing the composite parts of a SQL query.
  * @return
  *   None.
  */
 function hook_query_alter(QueryAlterableInterface $query) {
-  
+
 }
 
 /**
@@ -1397,7 +1397,7 @@ function hook_query_alter(QueryAlterable
  *
  * @see hook_query_alter()
  * @see node_query_node_access_alter()
- * 
+ *
  * @param $query
  *   An Query object describing the composite parts of a SQL query.
  * @return
@@ -1430,7 +1430,7 @@ function hook_query_TAG_alter(QueryAlter
       if (count($or->conditions())) {
         $query->condition($or);
       }
-      
+
       $query->condition("{$access_alias}.grant_$op", 1, '>=');
     }
   }
@@ -1577,5 +1577,140 @@ function hook_disable() {
 }
 
 /**
+ * Define one or more slots for the handlers system.
+ *
+ * This hook should return a nested array of slot definitions.  Each key
+ * in the array is the machine-readable slot name, and its value is an array
+ * of values that define the slot.
+ *
+ * title
+ *   This is the human-readable name of the slot. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * description
+ *   A human-readable description of what the slot is for. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * interface
+ *   The PHP interface that defines this slot.  The interface must extend
+ *   HandlerInterface, but otherwise may define whatever methods it wants.
+ *   It may also be placed in any file, depending on what would provide the
+ *   most performance for autoloading.
+ * default_handler (optional)
+ *   The machine-readable name of the default handler for this slot, as defined
+ *   in hook_handler_info().  All slots must have at least one handler
+ *   available.  If not specified, "default" is used.
+ * factory (optional)
+ *   The name of a function that serves as the factory for this slot. The factory
+ *   is the function that is called to return the appropriate handler for a
+ *   slot and target.  In most cases it will not be called directly but will
+ *   be sub-called from handler() automatically.  If not specified, the default
+ *   factory function is handler_factory_default().  Use a custom factory if
+ *   you need different behavior than the default factory with regards to
+ *   caching, reuse of handlers, etc.  Factory functions must have the same
+ *   signature as handler_factory_default().
+ *
+ * This is a registry-style function.  It should normally be placed in the
+ * file $module.registry.inc.
+ *
+ * @return
+ *   A slot definition array.
+ */
+function hook_slot_info() {
+  return array(
+    'fancystring' => array(
+      'title' => 'Fancy string',
+      'description' => 'Do fancy stuff to strings',
+      'interface' => 'FancystringInterface',
+      'default_handler' => 'default',
+    ),
+    'other' => array(
+      'title' => 'Other handler',
+      'description' => 'Dummy',
+      'targets' => array(),
+      'interface' => 'OtherInterface',
+      'factory' => 'custom_function',
+    ),
+  );
+}
+
+/**
+ * Alter the slot definitions of another module.
+ *
+ * @param $info
+ *   The complete slot definition array from hook_slot_info().
+ */
+function hook_slot_info_alter(&$info) {
+// Set a custom factory for the fancystring slot.
+  $info['fancystring']['factory'] = 'string_mangler';
+}
+
+/**
+ * Define one or more handlers in the system.
+ *
+ * This hook should return a doubly-nested array of handler definitions.  The
+ * first key is an existing slot.  Its value is an associative array of handler
+ * machine-readable names and handler definitions.
+ *
+ * Each handler definition is an associative array of values that define the
+ * handler.
+ *
+ * title
+ *   This is the human-readable name of the handler. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * description
+ *   A human-readable description of the handler. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * class
+ *   The PHP class that defines this handler.  The class must implement the
+ *   interface defined for this slot, but otherwise may be defined in any way
+ *   desired, including implementing the interface directly or indirectly by
+ *   subclassing another class that implements that interface. It may also be
+ *   placed in any file, depending on what would provide the most performance
+ *   for autoloading.
+ *
+ * This is a registry-style function.  It should normally be placed in the
+ * file $module.registry.inc.
+ *
+ * @return
+ *   A handler definition array.
+ */
+function hook_handler_info() {
+  return array(
+    'fancystring' => array(
+       'default' => array(
+         // Translate on load, not define, like hook_menu().
+         'title' => 'No string mutation',
+         'class' => 'FancystringDefault',
+         'description' => "This handler doesn't do anything to the string.  It passes through unaltered.",
+       ),
+       'rot13' => array(
+         'title' => 'Rot13 translation',
+         'class' => 'FancystringRot13',
+         'description' => 'This handler ROT13 encrypts a string.',
+       ),
+       'custom_translate' => array(
+         'title' => 'Custom mapping',
+         'class' => 'FancystringCustom',
+         'description' => 'This handler uses a user-specified mapping array.',
+       ),
+     ),
+  );
+}
+
+/**
+ * Alter the handler definitions of another module.
+ *
+ * @param $info
+ *   The complete handler definition array from hook_handler_info().
+ */
+function hook_handler_info_alter(&$info) {
+  // change the class used by the rot13 handler.
+  $info['fancystring']['rot13']['class'] = 'FancystringRot13Alternate';
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.301
diff -u -p -r1.301 system.install
--- modules/system/system.install	21 Jan 2009 16:58:42 -0000	1.301
+++ modules/system/system.install	25 Jan 2009 02:33:33 -0000
@@ -293,7 +293,7 @@ function system_install() {
   if (db_driver() == 'pgsql') {
     // We create some functions using global names instead of prefixing them
     // like we do with table names. If this function is ever called again (for
-    // example, by the test framework when creating prefixed test databases), 
+    // example, by the test framework when creating prefixed test databases),
     // the global names will already exist. We therefore avoid trying to create
     // them again in that case.
 
@@ -729,6 +729,110 @@ function system_schema() {
       'nid' => array('nid'),
     ),
   );
+
+  $schema['handler_attachments'] = array(
+    'description' => 'Maps slots and targets to their configured handler.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'target' => array(
+        'description' => 'The target value.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class for the handler attached to this slot/target.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('slot', 'target'),
+  );
+
+  $schema['handler_info'] = array(
+    'description' => 'Tracks all available handlers in the system.',
+    'fields' => array(
+      'handler' => array(
+        'description' => 'The machine-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot this handler is for.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class that defines this handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this handler.',
+        'type' => 'text',
+        'not null' => TRUE,
+      )
+    ),
+    'primary key' => array('handler', 'slot'),
+  );
+
+  $schema['handler_slot_info'] = array(
+    'description' => 'Tracks all registered slots in the system that handlers can fulfill.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'interface' => array(
+        'description' => 'The PHP interface that defines this slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'factory' => array(
+        'description' => 'The factory function used to generate new handlers for this slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'default_handler' => array(
+        'description' => 'The default handler id for this slot if one is not otherwise configured.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this slot.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('slot'),
+  );
+
   $schema['menu_router'] = array(
     'description' => 'Maps paths to various callbacks (access, page and title)',
     'fields' => array(
@@ -3140,11 +3244,11 @@ function system_update_7016() {
   $ret = array();
   // Only run these queries if the driver used is pgsql.
   if (db_driver() == 'pgsql') {
-    $result = db_query("SELECT c.relname AS table, a.attname AS field, 
-                        pg_catalog.format_type(a.atttypid, a.atttypmod) AS type 
-                        FROM pg_catalog.pg_attribute a 
-                        LEFT JOIN pg_class c ON (c.oid =  a.attrelid) 
-                        WHERE pg_catalog.pg_table_is_visible(c.oid) AND c.relkind = 'r' 
+    $result = db_query("SELECT c.relname AS table, a.attname AS field,
+                        pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
+                        FROM pg_catalog.pg_attribute a
+                        LEFT JOIN pg_class c ON (c.oid =  a.attrelid)
+                        WHERE pg_catalog.pg_table_is_visible(c.oid) AND c.relkind = 'r'
                         AND pg_catalog.format_type(a.atttypid, a.atttypmod) LIKE '%unsigned%'");
     while ($row = db_fetch_object($result)) {
       switch ($row->type) {
@@ -3157,7 +3261,7 @@ function system_update_7016() {
           $datatype = 'bigint';
           break;
       }
-      $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ALTER COLUMN ' . $row->field . ' TYPE ' . $datatype); 
+      $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ALTER COLUMN ' . $row->field . ' TYPE ' . $datatype);
       $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ADD CHECK (' . $row->field . ' >= 0)');
     }
     $ret[] = update_sql('DROP DOMAIN smallint_unsigned');
@@ -3197,6 +3301,122 @@ function system_update_7017() {
 }
 
 /**
+ * Add the new tables to support handlers.
+ */
+function system_update_7018() {
+  $ret = array();
+
+  $schema['handler_attachments'] = array(
+    'description' => 'Maps slots and targets to their configured handler.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'target' => array(
+        'description' => 'The target value.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class for the handler attached to this slot/target.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('slot', 'target'),
+  );
+
+  $schema['handler_info'] = array(
+    'description' => 'Tracks all available handlers in the system.',
+    'fields' => array(
+      'handler' => array(
+        'description' => 'The machine-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot this handler is for.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class that defines this handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this handler.',
+        'type' => 'text',
+        'not null' => TRUE,
+      )
+    ),
+    'primary key' => array('handler', 'slot'),
+  );
+
+  $schema['handler_slot_info'] = array(
+    'description' => 'Tracks all registered slots in the system that handlers can fulfill.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'interface' => array(
+        'description' => 'The PHP interface that defines this slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'factory' => array(
+        'description' => 'The factory function used to generate new handlers for this slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'default_handler' => array(
+        'description' => 'The default handler id for this slot if one is not otherwise configured.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this slot.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('slot'),
+  );
+
+  foreach ($schema as $table => $info) {
+  	db_create_table($ret, $table, $info);
+  }
+
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
