/[drupal]/contributions/modules/handler/handler.module
ViewVC logotype

Diff of /contributions/modules/handler/handler.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.11.2.5, Tue Dec 2 07:22:14 2008 UTC revision 1.11.2.5.2.1, Fri May 1 03:51:26 2009 UTC
# Line 1  Line 1 
1  <?php  <?php
2  // $Id: handler.module,v 1.11.2.4 2008/12/02 07:21:44 crell Exp $  // $Id: handler.module,v 1.11.2.5 2008/12/02 07:22:14 crell Exp $
3    
4  /**  /**
5   * @file   * @file
# Line 10  Line 10 
10   * for a given context, which will be returned as an object that may be invoked.   * for a given context, which will be returned as an object that may be invoked.
11   */   */
12    
 /**  
  * Include our additional libraries.  
  *  
  * These must be included before any hooks run, as some init hooks may depend  
  * on them.  
  */  
 require_once(drupal_get_path('module', 'handler') .'/handler.environment.inc');  
 require_once(drupal_get_path('module', 'handler') .'/handler.classes.inc');  
   
 /**  
  * Implementation of hook_init().  
  *  
  * This is just for development.  In practice we would only want to do this on  
  * hook_flush_caches(), not every page load.  
  */  
 function handler_init() {  
   handler_rebuild();  
 }  
   
 /**  
  * Implementation of hook_flush_caches().  
  */  
 function handler_flush_caches() {  
   handler_rebuild();  
 }  
   
 /**  
  * Rebuild the handler and slot registries.  
  */  
 function handler_rebuild() {  
   $slots = handler_slot_build();  
   $handlers = handler_handler_build();  
   handler_lookup_build();  
 }  
   
 /**  
  * Rebuild the lookup table used for handler mapping.  
  *  
  */  
 function handler_lookup_build() {  
   
   $target_order = variable_get('handler_slot_targets', array());  
   
   // Now go through all the saved attachment directives and build out the lookup table.  
   $result = db_query("SELECT slot_id, handler_id, targets FROM {handler_attachments}");  
   
   db_query("DELETE FROM {handler_lookup}");  
   
   while ($record = db_fetch_object($result)) {  
     $record->targets = unserialize($record->targets);  
     $fields = array(  
       'slot' => $record->slot_id,  
       'handler' => $record->handler_id,  
       'specificity' => count($record->targets),  
       'handler_class' => handler_load($record->slot_id, $record->handler_id)->class,  
     );  
   
     foreach ($record->targets as $target => $value) {  
       // The target order array tracks the mapping of target key to db field.  
       $fields[$target_order[$record->slot_id][$target]] = $value;  
     }  
   
     // Build the query.  I want the D7 query builder! :-(  
     $query_fields = implode(',', array_keys($fields));  
     $placeholders = implode(',', array_fill(0, count($fields), "'%s'"));  
     $values = array_values($fields);  
   
     // Insert this lookup record into the table.  
     db_query("INSERT INTO {handler_lookup} ({$query_fields}) VALUES ({$placeholders})", $values);  
   }  
   
   // Now insert a record for the default handler for each slot.  That way the  
   // lookup routine can always find at least one record.  
   $result = db_query("SELECT hsi.slot AS slot, hi.handler, class FROM {handler_info} hi INNER JOIN {handler_slot_info} hsi ON hi.slot=hsi.slot AND hi.handler=hsi.default_handler");  
   while ($record = db_fetch_array($result)) {  
     // A 0 specificity will always match as a last case.  
     $record[] = 0;  
     db_query("INSERT INTO {handler_lookup} (slot, handler, handler_class, specificity) VALUES ('%s', '%s', '%s', %d)", $record);  
   }  
 }  
   
 /**  
  * 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);  
   
   // Don't do the deletion until after we've gotten the new data.  That reduces  
   // the potential race condition window.  
   db_query('DELETE FROM {handler_info}');  
   
   // Build the handler data.  
   foreach ($handlers as $slot => $handler_info) {  
     foreach ($handler_info as $handler => $info) {  
       db_query("INSERT INTO {handler_info} (handler, slot, title, class, description)  
                             VALUES ('%s', '%s', '%s', '%s', '%s')",  
               array($handler, $slot, $info['title'], $info['class'], $info['description'])  
       );  
     }  
   }  
   
   return $handlers ;  
 }  
   
   
 /**  
  * Rebuild the handler slot info registry.  
  *  
  * @return  
  *   The array of slots just declared.  
  */  
 function handler_slot_build() {  
   
   $slots = module_invoke_all('handler_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('handler_slot_info', $slots);  
   
   // Don't do the deletion until after we've gotten the new data.  That reduces  
   // the potential race condition window.  
   db_query('DELETE FROM {handler_slot_info}');  
   
   $target_order = array();  
   
   // Build the target data.  
   foreach ($slots as $slot => $info) {  
     db_query("INSERT INTO {handler_slot_info} (slot, title, interface, factory, default_handler, targets, description)  
                           VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s')",  
             array($slot, $info['title'], $info['interface'], $info['factory'], $info['default_handler'], serialize($info['targets']), $info['description'])  
     );  
   
     // Determine the order of all possible targets, as this will be the order  
     // used in the database.  
     $targets = array_keys($info['targets']);  
     ksort($targets);  
     $i = 1;  
     foreach ($targets as $target) {  
       $target_order[$slot][$target] = 't'. $i++;  
     }  
   }  
   
   variable_set('handler_slot_targets', $target_order);  
   
   return $slots;  
 }  
   
 /**  
  * 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_generic',  
         'default_handler' => 'default',  
     'targets' => array(),  
     'description' => '',  
   );  
 }  
   
 /**  
  * Define various default values for handlers.  
  *  
  * @return  
  *   The default "empty" handler definition.  
  */  
 function handler_handler_defaults() {  
   return array(  
     'handler' => '',  
     'slot' => '',  
     'title' => '',  
     'class' => '',  
     'description' => '',  
   );  
 }  
   
 /**  
  * 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 $options  
  *   The options array determines which handler object should be used, depending  
  *   on the current configuration.  
  * @return  
  *   The handler object required.  
  */  
 function handler($slot_id, $options = array()) {  
   $function = handler_get_factory($slot_id);  
   if (function_exists($function)) {  
     return $function($options, $slot_id);  
   }  
13    
   return NULL;  
 }  
14    
15  /**  /**
16   * Returns the factory function for a given target.   * @ingroup handlers
17   *   * @{
  * If no factory is defined or the function does not exist, a generic  
  * factory is returned instead.  
  *  
  * @param $slot_id  
  *   The internal ID of the target.  
  * @return  
  *   The name of the factory function as a string.  
18   */   */
 function handler_get_factory($slot_id) {  
   
   $slot = handler_slot_load($slot_id);  
   
   return ($slot->factory && function_exists($slot->factory)) ? $slot->factory : 'handler_factory_generic';  
 }  
   
 /**  
  * 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 handler_slot_load($slot_id, $refresh = FALSE) {  
   static $slots;  
   
   if ($refresh) {  
     $slots = array();  
   }  
   
   if (empty($slots[$slot_id])) {  
     $slot = db_fetch_object(db_query("SELECT slot, title, interface, factory, default_handler, targets, description FROM {handler_slot_info} WHERE slot='%s'", array($slot_id)));  
     $slot->targets = unserialize($slot->targets);  
     $slots[$slot_id] = $slot;  
   }  
   
   return $slots[$slot_id];  
 }  
19    
20  /**  /**
21   * Load function for handler info objects.   * Request a handler object.
22   *   *
23   * @param $slot_id   * @param $slot_id
24   *   The internal slot ID.  All handlers are namespaced within their slot.   *   The machine-readable name of the slot the handler belongs to.
25   * @param $handler_id   * @param $target
26   *   The internal handler ID.   *   The target inside the subsytem the handler belongs to.
27   * @param $refresh   * @param $reset
28   *   If this is set to TRUE, the internal handler cache will be flushed.   *   If this is set to TRUE, the internal caches are flushed. Internal use
29   * @return   *   only.
30   *   The handler object or NULL if not defined.   *
31   */   * @return
32  function handler_load($slot_id, $handler_id, $refresh = FALSE) {   *   The associated handler object.
33    static $handlers;   *
34     * @see hook_slot_info()
35    if ($refresh) {   * @see hook_handler_info()
36      $handlers = array();   */
37    }  function handler($slot_id, $target = 'default', $reset = FALSE) {
38      // $handler_mappings is an array, keyed by $slot_id and $target and the
39    if (empty($handlers[$handler_id])) {    // values are arrays containing class names and a boolean indicating
40      $handler = db_fetch_object(db_query("SELECT handler, slot, title, class, description FROM {handler_info} WHERE slot='%s' AND handler='%s'", array($slot_id, $handler_id)));    // reusability. handler_objects keeps the objects themselves.
41      $handlers[$handler_id] = $handler;    // $default_only[$slot_id] is TRUE if the given slot only uses defaults.
42      static $handler_mappings, $handler_objects, $default_only;
43    
44      // We have to reset the target cache after a new handler has been attached
45      // or detached.
46      if ($reset) {
47        $handler_mappings = array();
48        $handler_objects = array();
49        $default_only = array();
50        return;
51      }
52    
53      // If we already have the appropriate handler cached, just use that.
54      if (!empty($handler_objects[$slot_id][$target])) {
55        return $handler_objects[$slot_id][$target];
56      }
57    
58      // While the objects need to be stored per target, the classnames can be per
59      // slot, if the only attached handler is attached to default. That includes
60      // any slot that does not make use of targets. We cache the handler
61      // information for these slots in the variable system to reduce database
62      // lookups. Because the variable system doesn't auto-initialize in
63      // variable_get(), any handler lookup that runs before the variable system
64      // has been initialized will simply fail this check and use the database
65      // anyway.
66      $mapping_target = isset($default_only[$slot_id]) ? 'default' : $target;
67      // Look up the class for the requested slot/target.
68      if (empty($handler_mappings[$slot_id][$mapping_target])) {
69        // If the only attached handler is attached to default,
70        if ($record = variable_get('handler_default_' . $slot_id, array())) {
71          $default_only[$slot_id] = TRUE;
72          $mapping_target = 'default';
73        }
74        else {
75          // Try to get the associated handler. If the first query finds an associated
76          // handler, we use that. If not, the second query will always find the
77          // default handler. By UNIONing them together we get the fallback default
78          // behavior without having to issue a second request to the database.
79          $record = db_query_range("SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_1 AND target = :target
80            UNION SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_2 AND target = 'default'", array(
81            ':slot_1' => $slot_id,
82            ':slot_2' => $slot_id,
83            ':target' => $target,
84          ), 0, 1)->fetchAssoc();
85    
86          // It's possible that this function will be called before the handler system
87          // has first been initialized. That's especially the case for early-running
88          // systems such as cache or path, as they operate during the bootstrap phase.
89          // If we have no record at all, we first rebuild the registry and then try
90          // again. That should at least always give us the slot-defined default
91          // and allow the system to proceed.
92          if (!$record) {
93            registry_rebuild();
94            handlers_rebuild();
95            $record = db_query_range("SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_1 AND target = :target
96              UNION SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_2 AND target = 'default'", array(
97              ':slot_1' => $slot_id,
98              ':slot_2' => $slot_id,
99              ':target' => $target,
100            ), 0, 1)->fetchAssoc();
101          }
102        }
103        // Statically cache the lookup information so that we don't need to check
104        // for it again.
105        $handler_mappings[$slot_id][$mapping_target] = $record;
106      }
107    
108      // Create a new handler object.
109      $handler = new $handler_mappings[$slot_id][$mapping_target]['class']($target);
110    
111      // Cache the handler object for later use, if flagged to do so.
112      if ($handler_mappings[$slot_id][$mapping_target]['reuse']) {
113        $handler_objects[$slot_id][$target] = $handler;
114    }    }
115      return $handler;
   
   return $handlers[$handler_id];  
116  }  }
117    
118  /**  /**
119   * The generic handler factory.   * Rebuild the handler and slot registries.
  *  
  * In most cases, this factory will be sufficient for handlers that do not  
  * need a new object generated on each call.  
  *  
  * @param $options  
  *   The options array 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.  
120   */   */
121  function handler_factory_generic(Array $options = array(), $slot_id) {  function handlers_rebuild() {
122      require_once DRUPAL_ROOT . '/includes/handlers.inc';
123    static $handlers;    handler_slot_rebuild();
124      handler_handler_rebuild();
125    static $env;    handler_ensure_defaults();
   
   // We only need one environment variable in the default case.  
   if (empty($env)) {  
     $env = new EnvironmentDefault();  
   }  
   
   $class = handler_get_registered_handler($slot_id, $options);  
   
   // If we haven't already created this handler, instantiate a new object for it.  
   if (empty($handlers[$class])) {  
     $handler = new $class();  
     $handler->setEnvironment($env);  
   
     $handlers[$class] = $handler;  
   }  
   
   return $handlers[$class];  
126  }  }
127    
128  /**  /**
129   * Derive the handler class for the specified target.   * Interface for all handler classes for any slot.
130   *   *
131   * @param $target_id   * This is a very thin interface, but does serve to help standardize handler
132   *   The internal ID of the target.   * behavior and provides a way to type-check a handler object. Interfaces for
133   * @param $targets   * specific slots should extend this interface.
  *   The array of targets to help route this handler request.  If it does not  
  *   have a value specified for every target, it will be filled in with the  
  *   slot-defined defaults.  
  * @return  
  *   The name of the class that should be loaded for this  
  *   target/option configuration.  
134   */   */
135  function handler_get_registered_handler($slot_id, $targets) {  interface HandlerInterface {
   
   // We maintain an alphabetical order in the lookup table so that everything  
   // matches up properly.  
   ksort($targets);  
   
   $sql = "SELECT handler_class FROM {handler_lookup} WHERE ";  
   
   $index = 1;  
   $where = array();  
   $values = array();  
   foreach ($targets as $target => $value) {  
     $field = 't'. $index++;  
     $where[] = '('. $field ."='%s' OR ". $field .' IS NULL)';  
     $values[] = $value;  
   }  
   $where[] = 'specificity <= %d';  
   $values[] = count($targets);  
   $sql .= implode(' AND ', $where);  
   
   // This last check will always match the default  
   $sql .= ' OR specificity = 0';  
   
   $sql .= " ORDER BY specificity DESC";  
   
   $class = db_result(db_query_range($sql, $values, 0, 1));  
   
   return $class;  
   
   /*  
   $mapping = handler_slot_mapping($slot_id);  
136    
137    $targets += handler_default_targets($slot_id);    /**
138       * Constructor
139    // Because we need to traverse down the options array somehow, we normalize     *
140    // the array order to alphabetic by the option key.  That allows us to walk     * @param $target
141    // the array in a standardized order.     *   The target for which this handler is being called. Some handlers may
142    ksort($options);     *   require this information in order to route commands properly.
143    $handler_id = &$mapping;     */
144    foreach ($options as $option => $value) {    function __construct($target = 'default');
     if (isset($handler_id[$value])) {  
       $handler_id = &$handler_id[$value];  
     }  
     else {  
       $slot = handler_target_load($slot_id);  
       $handler_id = $slot->default_handler;  
       break;  
     }  
   
   }  
   
   $handler = handler_load($handler_id);  
   
   return $handler->class;  
   */  
 }  
   
 function handler_slot_mapping($slot_id) {  
   
   $mapping = db_result(db_query("SELECT mapping FROM {handler_mapping} WHERE target='%s'", array($target_id)));  
   
   if ($mapping) {  
     $mapping = unserialize($mapping);  
   }  
   else {  
     $mapping = array();  
   }  
   
   return $mapping;  
145  }  }
146    
147  /**  /**
148   * Asociate a given handler to a give slot for the specified targets.   * Base implementation of a handler object.
149   *   *
150   * @param $slot_id   * Simple handler objects may choose to inherit from a base class in order to
151   *   The internal ID of the slot.   * not reimplement routine functionality. Alternatively they may simply implement
152   * @param $handler_id   * the appropriate interface and implement their own version of common
153   *   The internal ID of the hand   * functionality. Both methods are acceptable depending on the use case.
  * @param $options  
  *   The array of options  
154   */   */
155  function handler_attach($slot_id, $handler_id, $targets) {  abstract class HandlerBase implements HandlerInterface {
   
   db_query("INSERT INTO {handler_attachments} (slot_id, handler_id, targets) VALUES ('%s', '%s', '%s')", array(  
     $slot_id, $handler_id, serialize($targets)  
   ));  
   
   /*  
   $mapping = db_result(db_query("SELECT mapping FROM {handler_mapping} WHERE slot='%s'", array($slot_id)));  
   
   $is_new = FALSE;  
   if ($mapping) {  
     $mapping = unserialize($mapping);  
   }  
   else {  
     $mapping = array();  
     $is_new = TRUE;  
   }  
   
   $options += handler_default_options($slot_id);  
156    
157    ksort($options);    /**
158    $map_point = &$mapping;     * The target for which this handler is active.
159    foreach ($options as $key => $value) {     */
160      if (empty($map_point[$value])) {    protected $target;
       $map_point[$value] = array();  
     }  
     $map_point = &$map_point[$value];  
   }  
   
   $map_point = $handler_id;  
161    
162    // It would be nicer do a delete/insert cycle, but that would result in a race    function __construct($target = 'default') {
163    // condition.  What we really want here is a merge query, but those will have      $this->target = $target;
   // to wait until Drupal 7.  
   if ($is_new) {  
     db_query("INSERT INTO {handler_mapping} (slot, mapping) VALUES ('%s', '%s')", array($slot_id, serialize($mapping)));  
164    }    }
   else {  
     db_query("UPDATE {handler_mapping} SET mapping='%s' WHERE slot='%s'", array(serialize($mapping), $slot_id));  
   }  
   */  
165  }  }
166    
167  /**  /**
168   * Get the default targets for a given slot.   * @} End of "ingroup handlers".
  *  
  * @param $slot_id  
  *   The internal ID of the slot.  
  * @return  
  *   An associative array of the default targets for the specified slot.  
169   */   */
 function handler_default_targets($slot_id) {  
   $slot = handler_slot_load($slot_id);  
   
   return $slot->targets;  
 }  
   

Legend:
Removed from v.1.11.2.5  
changed lines
  Added in v.1.11.2.5.2.1

  ViewVC Help
Powered by ViewVC 1.1.2