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 = '' . "\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 @@
+ t("ESI"),
+ 'description' => t('ESI caching is a proxy-based cache. Panes are replaced by 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;
+}