Index: esi.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/esi/esi.module,v
retrieving revision 1.2
diff -u -p -r1.2 esi.module
--- esi.module	17 Nov 2010 11:37:25 -0000	1.2
+++ esi.module	29 Nov 2010 16:17:43 -0000
@@ -6,6 +6,9 @@
  *   delivered by ESI, with support for per-block cache times.
  */
 
+// Tested against 1.7.2 of the ctools API.
+define('ESI_REQUIRED_CTOOLS_API', '1.7.2');
+
 // Default interval for rotating the seed key: defaults to change-daily.
 define('ESI__DEFAULT_SEED_KEY_ROTATION_INTERVAL', 86400);
 
@@ -57,6 +60,13 @@ function esi_menu() {
       'page arguments' => array(2),
       'access callback' => TRUE,
       'type' => MENU_CALLBACK
+    ),
+    'esi/panels_pane/%/%' => array(
+      'title' => 'ESI handler',
+      'page callback' => 'esi__panel_pane_handler',
+      'page arguments' => array(2, 3),
+      'access callback' => TRUE,
+      'type' => MENU_CALLBACK
     )
   );
 }
@@ -251,3 +261,152 @@ function esi__block_handler($bid, $page 
   echo $output;
   return NULL;
 }
+
+/**
+ * Implementation of hook_ctools_plugin_directory().
+ */
+function esi_ctools_plugin_directory($module, $plugin) {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', ESI_REQUIRED_CTOOLS_API)) {
+    return;
+  }
+  if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools') {
+    return 'plugins/' . $plugin;
+  }
+}
+
+/**
+ * Implementation of hook_pane_content_alter().
+ *
+ * If the pane isn't being served up by the ESI menu handler, and is set to use
+ * ESI-caching, replace with ESI tag.
+ * This needs to be handled in hook_pane_content_alter() - i.e. after the
+ * content has been processed - because the cache object needs the meta-data
+ * provided by ctype-render handler, such as module, delta, etc.
+ * Subsequent requests are pulled from the system-cache.
+ */
+function esi_panels_pane_content_alter($content, $pane, $args, $context) {
+  // Bail out if it's not handled by ESI.
+  if(!is_array($pane->cache) || $pane->cache['method'] != 'esi') {
+    return;
+  }
+
+  $display = panels_get_current_page_display();
+  // The cache-key variable is set by the panel-context render function.
+  list($context, $task_name, $handler_name) = explode(':', $display->cache_key);
+
+  // Build the esi tag.
+  $url = "esi/panels_pane/{$pane->did}/{$pane->pid}";
+  if(array_key_exists('context', $pane->configuration)) {
+    // Add the page-manager task name.
+    $url .= '/' . $task_name;
+
+    // Add the name of the context which is supplied to this pane.
+    $url .= '/' . $pane->configuration['context'];
+
+    // Add the URL the panel was requested from.
+    $url .= '/' . base64_encode($_GET['q']);
+  }
+  $url = url($url);
+  $esi_tag = '<esi src="' . $url . '" />' . "\n";
+
+  $content->content = $esi_tag;
+}
+
+
+/**
+ * Menu handler to serve individual panel-panes via ESI.
+ *
+ * If the pane uses context, the task_name, context_string and q variables will
+ * be set.
+ */
+function esi__panel_pane_handler($display_id, $pane_id, $task_name = NULL, $context_string = NULL, $q = NULL) {
+  $q = base64_decode($q);
+  $_GET['q'] = $q;
+
+  $display = panels_load_display($display_id);    
+
+  if(!is_null($task_name)) {
+    // Get the context for this pane.
+    list($args, $contexts) = esi__panels_get_task_context($task_name);
+
+    $display->context = $contexts;
+    $display->args = $args;
+  }
+
+  // Switch ESI off so the contents of the pane are served.
+  unset($display->content[$pane_id]->cache);
+
+  // Use the standard renderer to render the pane.
+  $renderer = panels_get_renderer_handler('standard', $display);
+  $renderer->prepare_panes($renderer->display->content);
+  $content = $renderer->render_pane($renderer->prepared['panes'][$pane_id]);
+
+  echo $content;
+
+  return NULL;
+}
+
+/**
+ * Each of the panel task plugins provides a default context based on the menu
+ * path.
+ * This function looks up the menu handler for a URL, and provides the contexts
+ * for the menu-handler.
+ */
+function esi__panels_get_task_context($task_name) {
+  $task = page_manager_get_task($task_name);
+
+  // Invoke the module's hook_esi_get_context_arguments to get the context
+  // provided by that task.
+  $context_arguments = module_invoke($task['module'], 'esi_get_context_arguments', $task['name']);
+
+  // Parse the arguments into context objects.
+  ctools_include('context');
+  ctools_include('context-task-handler');
+  $contexts = ctools_context_handler_get_task_contexts($task, '', $context_arguments);
+
+  return array($context_arguments, $contexts);
+}
+
+/**
+ * Implementation of hook_esi_get_context, provided for page_manager.
+ */
+function page_manager_esi_get_context_arguments($task_name) {
+  switch($task_menu_callback) {
+    // The blog, poll, and contact_site tasks don't provide default context.
+    case 'blog':
+    case 'poll':
+    case 'contact_site':
+      return array();
+
+    // The blog_user, and contact_user tasks provide a user-object.
+    case 'blog_user':
+    case 'contact_user':
+      $uid = arg(1);
+      $account = user_load($uid);
+      return array($account);
+
+    // The comment_reply task provide a node object and a comment CID.
+    case 'comment_reply':
+      // Path is comment/reply/%node
+      $nid = arg(2);
+      $pid = arg(3);
+      $node = node_load($nid);
+      return array($node, $pid);
+
+      // The node_edit and node_view tasks provide a node object.
+    case 'node_edit':
+    case 'node_view':
+      $nid = arg(1);
+      $node = node_load($nid);
+      return array($node);
+
+    case 'search':
+      // @TODO.
+      // return array($keys);
+
+    case 'term_view':
+      // @TODO.
+      //  return array($terms, $depth);
+  }
+}
Index: plugins/cache/esi.inc
===================================================================
RCS file: plugins/cache/esi.inc
diff -N plugins/cache/esi.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ plugins/cache/esi.inc	29 Nov 2010 16:17:43 -0000
@@ -0,0 +1,98 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * ESI cache plugin.  Substitutes esi-tags for a panel-pane's content.
+ */
+
+// Plugin definition
+$plugin = array(
+  'title' => t("ESI"),
+  'description' => t('ESI caching is a proxy-based cache. Panes are replaced by <esi> tags and requested separately by the proxy.'),
+  'cache get' => 'esi_esi_cache_get_cache',
+  'cache set' => 'esi_esi_cache_set_cache',
+//  'cache clear' => 'esi_esi_cache_clear_cache',
+  'settings form' => 'esi_esi_cache_settings_form',
+  'settings form submit' => 'esi_esi_cache_settings_form_submit',
+  'defaults' => array(
+    // TODO: review settings.
+    'use_esi' => FALSE, // The use_esi flag is set to TRUE in pre-render, so it's only used when viewing a complete panel.
+    'lifetime' => 3600,   // Lifetime measured in seconds. 3600 = 1 hour.  Sent by ESI callback as cache-control header.
+  ),
+);
+
+/**
+ * Get cached content.
+ */
+function esi_esi_cache_get_cache($conf, $display, $args, $contexts, $pane = NULL) {
+  // If the pane is being rendered directly through panels, 'use_esi' will be
+  // set to TRUE (by hook_panels_pre_render).  If it's being rendered via 
+  // hook_menu as an ESI callback, 'use_esi' will be FALSE and we should return
+  // FALSE here so that it's rendered normally.
+
+  $cid = esi_esi_cache_get_id($conf, $display, $args, $contexts, $pane);
+  $cache = cache_get($cid, 'cache');
+  if (!$cache) {
+    return FALSE;
+  }
+
+  if ((time() - $cache->created) > $conf['lifetime']) {
+    return FALSE;
+  }
+
+  return $cache->data;
+}
+
+/**
+ * Set cached content.
+ */
+function esi_esi_cache_set_cache($conf, $content, $display, $args, $contexts, $pane = NULL) {
+  $cid = esi_esi_cache_get_id($conf, $display, $args, $contexts, $pane);
+  cache_set($cid, $content);
+}
+
+/**
+ * Clear cached content.
+ *
+ * Cache clears are always for an entire display, regardless of arguments.
+ */
+function esi_esi_cache_clear_cache($display) {
+  // TODO: wipe the varnish cache.
+}
+
+/**
+ * Figure out an id for our cache based upon input and settings.
+ */
+function esi_esi_cache_get_id($conf, $display, $args, $contexts, $pane) {
+  // Build the cache-key using display ID, pane ID and pane context (if set).
+  $key = array();
+  $key[] = 'esi_esi_cache';
+  $key[] = $display->id;
+  $key[] = $pane->pid;
+  if(array_key_exists('context', $pane->configuration)) {
+    $key[] = $pane->configuration['context'];
+  }
+
+  return implode(':', $key);
+}
+
+function esi_esi_cache_settings_form($conf, $display, $pid) {
+  // The ESI callback URL provides the display ID, pane ID, and the string
+  // identifier for the context passed to the pane.
+  // E.g. /esi/panel_panes/21/2
+  // E.g. /esi/panel_panes/21/3/argument_current_page_content_1
+
+
+
+  // The contexts-definition for this pane is in $display->configuration['context']
+  $options = drupal_map_assoc(array(15, 30, 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 3600, 7200, 14400, 28800, 43200, 86400, 172800, 259200, 345600, 604800), 'format_interval');
+  $form['lifetime'] = array(
+    '#title' => t('Lifetime'),
+    '#type' => 'select',
+    '#options' => $options,
+    '#default_value' => $conf['lifetime'],
+  );
+
+  return $form;
+}
