esi.module in ESI: Edge Side Includes 6.2
Same filename and directory in other branches
Adds support for ESI (Edge-Side-Include) integration, allowing blocks to be\ delivered by ESI, with support for per-block cache times.
File
esi.moduleView source
<?php
/**
* @file
* Adds support for ESI (Edge-Side-Include) integration, allowing blocks to be\
* 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 setting: enabled & ESI.
define('ESI_MODE', TRUE);
// Default to using ajax as a fallback if ESI is disabled.
define('ESI_AJAX_FALLBACK', TRUE);
// Default to not using a CDN for AJAX fragments.
define('ESI_CDN_AJAX', FALSE);
// Default to using the cache_page bin to hold ESI html for fast delivery.
define('ESI_PAGE_CACHE', TRUE);
// Default interval for rotating the seed key: defaults to change-daily.
define('ESI_SEED_ROTATION_INTERVAL', 86400);
// Default value to see if the CACHE=* is in place when the page is served.
define('ESI_ROLE_USER_DIRECT_INJECTION', FALSE);
// Default max age for blocks: 5 minutes.
define('ESI_BLOCK_DEFAULT_MAX_AGE', 300);
// Default scope for blocks: Disabled.
define('ESI_BLOCK_DEFAULT_SCOPE', 0);
// Default max age for panels: 5 minutes.
define('ESI_PANEL_DEFAULT_MAX_AGE', 300);
// Default scope for panels: User Role.
define('ESI_PANEL_DEFAULT_SCOPE', 4);
// ESI setting constants.
define('ESI__CONFIG_ESI', 1);
define('ESI__CONFIG_SSI', 2);
define('ESI__CONFIG_AJAX', 3);
// Setting that allows for the override of $_SERVER['REQUEST_URI'] when loading
// the context for blocks.
define('ESI_OVERRIDE_REQUEST_URI', FALSE);
/**
* Implementation of hook_theme().
*/
function esi_theme() {
return array(
'esi_tag' => array(
'arguments' => array(
'type' => NULL,
'data' => NULL,
),
'file' => 'esi.theme.inc',
),
);
}
/**
* Implementation of hook_theme_registry_alter().
* Override theme('blocks') to apply our ESI handler.
*/
function esi_theme_registry_alter(&$theme_registry) {
if (!variable_get('esi_mode', ESI_MODE)) {
return;
}
$path = drupal_get_path('module', 'esi');
$panels_callbacks = array(
'block',
'panels_pane',
'panels_stylizer_stylizer_style_render_pane',
'panels_rounded_corners_style_render_pane',
);
foreach ($panels_callbacks as $pane_callback) {
// override the default theme function for theme('panels_pane').
if (isset($theme_registry[$pane_callback])) {
$theme_registry['esi_alt_' . $pane_callback] = $theme_registry[$pane_callback];
$theme_registry[$pane_callback]['function'] = 'esi_theme_' . $pane_callback;
$theme_registry[$pane_callback]['file'] = 'esi.theme.inc';
$theme_registry[$pane_callback]['include files'] = array(
"./{$path}/esi.theme.inc",
);
$theme_registry[$pane_callback]['theme path'] = $path;
$theme_registry[$pane_callback]['theme paths'] = array(
$path,
);
}
}
if (module_exists('context')) {
$name = 'context';
$context_path = drupal_get_path('module', $name) . '/' . $name . '.info';
$info = drupal_parse_info_file($context_path);
if (isset($info['version']) && strpos($info['version'], '6.x-3.') !== FALSE) {
return;
}
}
// override the default theme function for theme('blocks').
$theme_registry['blocks']['function'] = 'esi__theme_blocks';
$theme_registry['blocks']['file'] = 'esi.theme.inc';
$theme_registry['blocks']['include files'] = array(
"./{$path}/esi.theme.inc",
);
$theme_registry['blocks']['theme path'] = $path;
$theme_registry['blocks']['theme paths'] = array(
$path,
);
}
/**
* See if the block/pane has been esi-ed.
*
* @param $content
* html that gets passed to the theme function.
*/
function esi_theme_is_esied($content) {
// Content starts with "<esi:include src".
// OR content ends with "class="esi-ajax"></div>".
// OR content starts with "<!--# include virtual=".
if (strpos($content, '<esi:include src=') === 0 || strpos($content, 'class="esi-ajax"></div>') === strlen($content) - 23 || strpos($content, '<!--# include virtual=') === 0) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook_init().
*/
function esi_init() {
// Set RSESS/USESS cookie if not set and user is logged in.
global $user;
if (!empty($user->uid) && (empty($_COOKIE['R' . session_name()]) || empty($_COOKIE['U' . session_name()]))) {
$edit = '';
esi_user('login', $edit, $user);
}
// If ESI or AJAX Fallback is disabled then do not add in the esi.js file.
if (!variable_get('esi_mode', ESI_MODE) || !variable_get('esi_ajax_fallback', ESI_AJAX_FALLBACK)) {
return;
}
drupal_add_js(drupal_get_path('module', 'esi') . '/esi.js');
}
/**
* Implementation of hook_menu().
* Define a menu-handler.
*/
function esi_menu() {
$items = array();
$items['esi/block/%'] = array(
'title' => 'ESI handler',
'page callback' => 'esi__block_handler',
'page arguments' => array(
2,
3,
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['esi/panels_pane/%'] = array(
'title' => 'ESI handler',
'page callback' => 'esi__panel_pane_handler',
'page arguments' => array(
2,
3,
4,
5,
),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['admin/settings/esi'] = array(
'title' => 'ESI Settings',
'description' => 'Configure ESI Default Settings',
'page callback' => 'esi_admin_page',
'access arguments' => array(
'administer site configuration',
),
'file' => 'esi.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
$items['admin_menu/flush-cache/esi'] = array(
'page callback' => 'esi_admin_flush_cache',
'type' => MENU_CALLBACK,
'access arguments' => array(
'administer site configuration',
),
'file' => 'esi.admin.inc',
);
return $items;
}
/**
* Implementation of hook_admin_menu().
*
* Add in a cache flush for esi.
*/
function esi_admin_menu(&$deleted) {
$links = array();
$links[] = array(
'title' => 'ESI: cache_page',
'path' => 'admin_menu/flush-cache/esi',
'query' => 'destination',
'parent_path' => 'admin_menu/flush-cache',
);
return $links;
}
/**
* Implementation of hook_admin_menu_output_alter().
*
* Add in a cache flush for esi.
*/
function esi_admin_menu_output_alter(&$content) {
if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['esi'])) {
$content['icon']['icon']['flush-cache']['esi'] = $content['icon']['icon']['flush-cache']['requisites'];
$content['icon']['icon']['flush-cache']['esi']['#title'] = t('ESI: cache_page');
$content['icon']['icon']['flush-cache']['esi']['#href'] = 'admin_menu/flush-cache/esi';
}
}
/**
* Implementation of hook_cron().
* Every interval, rotate the seed (used to generate the role-cookie).
* (Each rotation will invalidate the varnish-cache for cached pre-role blocks).
*/
function esi_cron() {
$age = time() - variable_get('esi_seed_key_last_changed', 0);
$interval = variable_get('esi_seed_key_rotation_interval', ESI_SEED_ROTATION_INTERVAL);
if ($age > $interval) {
require_once drupal_get_path('module', 'esi') . '/esi.inc';
_esi__rotate_seed_key();
}
}
/**
* Implementation of hook_user().
* For maximum cache-efficiency, the proxy must be able to identify the roles
* held by a user. A cookie is used which provides a consistent hash for
* all users who share the same roles.
* For security, the hash uses a random seed which is rotated (by hook_cron)
* at regular intervals - defaults to daily.
*/
function esi_user($op, &$edit, &$account, $category = NULL) {
// only respond to login/logout.
if (!($op == 'login' || $op == 'logout')) {
return;
}
// Drupal session cookies use the name 'SESS' followed by an MD5 hash.
// The role-cookie is the same, prefixes with the letter 'R'.
$cookie_params = session_get_cookie_params();
$role_cookie = $cookie_params + array(
'name' => 'R' . session_name(),
);
$user_cookie = $cookie_params + array(
'name' => 'U' . session_name(),
);
if ($op == 'login') {
require_once drupal_get_path('module', 'esi') . '/esi.inc';
$role_hash = _esi__get_roles_hash(array_keys($account->roles));
$user_hash = esi_get_user_hash($account->uid);
$lifespan = max(variable_get('esi_seed_key_rotation_interval', ESI_SEED_ROTATION_INTERVAL), ini_get('session.cookie_lifetime'));
$role_cookie += array(
'value' => $role_hash,
'expire' => time() + $lifespan,
);
$user_cookie += array(
'value' => $user_hash,
'expire' => time() + $lifespan,
);
}
else {
$role_cookie += array(
'value' => 'deleted',
'expire' => 1,
);
$user_cookie += array(
'value' => 'deleted',
'expire' => 1,
);
}
drupal_alter('esi_role_cookie', $role_cookie, $op, $account);
drupal_alter('esi_user_cookie', $user_cookie, $op, $account);
setcookie($role_cookie['name'], $role_cookie['value'], $role_cookie['expire'], $role_cookie['path'], $role_cookie['domain']);
setcookie($user_cookie['name'], $user_cookie['value'], $user_cookie['expire'], $user_cookie['path'], $user_cookie['domain']);
}
/**
* Implementation of hook_form_FORM_ID_alter().
* for block_admin_configure
* Add ESI-configuration options to the block-config pages.
*/
function esi_form_block_admin_configure_alter(&$form, $form_state) {
// TODO: describe how the cache configs can be configured as defaults in code.
// load our helper functions
require_once drupal_get_path('module', 'esi') . '/esi.inc';
$module = $form['module']['#value'];
$delta = $form['delta']['#value'];
$config = esi_get_settings($module . '_' . $delta);
$element['esi_config'] = array(
'#title' => t('ESI settings'),
'#type' => 'fieldset',
'#description' => t('Control how this block is cached on an ESI-enabled reverse proxy.'),
'#tree' => TRUE,
);
$element['esi_config']['scope'] = array(
'#title' => t('Cache Scope'),
'#type' => 'select',
'#options' => array(
0 => 'Disabled',
1 => 'Not Cached',
2 => 'Global',
3 => 'Page',
4 => 'User Role',
5 => 'User Role/Page',
6 => 'User ID',
7 => 'User ID/Page',
),
'#default_value' => $config['scope'],
'#description' => t('Disabled - Do not use ESI. <br />Not Cached - Use ESI, but never cache the content. <br />Global - Content is same on every page. <br />Page - Content changes based on the URL. <br />User Role - Content changes based on the user role. <br />User Role/Page - Content changes based on the user role as well as the URL. <br />User ID - Content changes based on the UID; otherwise it is the same as global. <br />User ID/Page - Content changes based on the UID and based on the URL.'),
);
$max_age = $config['max_age'];
$element['esi_config']['max_age'] = array(
'#title' => t('Cache Maximum Age (TTL)'),
'#type' => 'select',
'#options' => esi_max_age_options($max_age),
'#default_value' => $max_age,
'#description' => t('External page caches (proxy/browser) will not deliver cached paged older than this setting; time to live in short.'),
);
$widget_vis = "\n\$(esi_update_visibility);\n\$(function(){ \$('#edit-esi-config-scope').change(function (){esi_update_visibility();})});\nfunction esi_update_visibility() {\n var esi_scope = \$('#edit-esi-config-scope').val();\n if (esi_scope == '0' || esi_scope == '1') {\n \$('#edit-esi-config-max-age-wrapper').hide();\n }\n else {\n \$('#edit-esi-config-max-age-wrapper').show();\n }\n}";
drupal_add_js($widget_vis, 'inline');
// inject our ESI config-fieldset onto the form,
// just after the 'block_settings' fieldset.
$i = 1;
foreach ($form as $key => $value) {
if ($key == 'block_settings') {
break;
}
$i++;
}
$f = array_slice($form, 0, $i);
$f += $element;
$f += array_slice($form, $i);
$form = $f;
// add a submit-handler to save our config.
$form['#submit'][] = 'esi__block_config_save';
}
/**
* Form-submit handler for ESI settings in block-config
*/
function esi__block_config_save($form, $form_state) {
require_once drupal_get_path('module', 'esi') . '/esi.inc';
$module = $form_state['values']['module'];
$delta = $form_state['values']['delta'];
$config = array();
$config['max_age'] = (int) $form_state['values']['esi_config']['max_age'];
$config['scope'] = (int) $form_state['values']['esi_config']['scope'];
// Save the settings.
esi_get_settings($module . '_' . $delta, $config);
}
/**
* Menu handler for ESIs
*
* Render a particular block.
*/
function esi__block_handler($bid, $page = NULL) {
// Expect the bid format to be theme:region:module:delta
if (substr_count($bid, ':') !== 3) {
return FALSE;
}
list($passed_theme, $region, $module, $delta) = explode(':', $bid);
require_once drupal_get_path('module', 'esi') . '/esi.inc';
global $custom_theme, $theme_key, $user;
// Save this page's internal URL.
$last_arg = explode('/', $_GET['q']);
$last_arg = array_pop($last_arg);
// Block content may change per-page.
// If this is true for the current block, the origin page url should be
// provided as an argument.
if (isset($page) && strpos($page, 'CACHE=') !== 0) {
$q = base64_decode($page);
if ($q == "") {
$q = variable_get('site_frontpage', 'node');
}
$_GET['q'] = $q;
if (variable_get('esi_override_request_uri', ESI_OVERRIDE_REQUEST_URI)) {
$_SERVER['REQUEST_URI'] = $GLOBALS['base_path'] . $q;
}
}
// Load up the ESI configuration for this block.
$config = esi_get_settings($module . '_' . $delta);
$current_user = $user;
// Use user 0 if the scope is global or page.
if ($config['scope'] == 2 || $config['scope'] == 3) {
$user = user_load(0);
}
// Theme set-up.
// We need to do this manually, because output is echo'd instead of returned.
$custom_theme = $passed_theme;
$theme_key = $passed_theme;
init_theme();
// Get the block and theme it.
$block = _esi__get_block($passed_theme, $region, $module, $delta);
if (!empty($block)) {
$output = theme('block', $block);
}
else {
$output = esi_fast404('block');
}
// Pass PER-USER or PER-ROLE cache info to varnish.
// No-cache is header max age (TTL) controlled.
// Per-page is passed as a url argument.
esi_add_cache_headers($config['scope'], $config['max_age']);
echo $output;
$user = $current_user;
esi_set_page_cache($output, $config['scope'], $last_arg);
exit;
}
function esi_add_cache_headers($scope, $max_age) {
// Nginx follows rfc2616 section 14.9.1 correctly and will not cache if
// header is set to private. The given Varnish VCL does not follow the RFC,
// and for ajax we want to use private.
$user_cache_control_header = variable_get('esi_mode', ESI_MODE) == ESI__CONFIG_SSI ? 'public' : 'private';
switch ($scope) {
// Disabled.
case 0:
// Do no cache.
case 1:
drupal_set_header("Cache-Control: must-revalidate, max-age=0");
break;
// Global Scope.
case 2:
// Page Scope.
case 3:
drupal_set_header("Cache-Control: public, max-age={$max_age}");
break;
// User Role.
case 4:
// User Role/Page.
case 5:
drupal_set_header("Cache-Control: {$user_cache_control_header}, max-age={$max_age}");
drupal_set_header("X-BLOCK-CACHE: " . BLOCK_CACHE_PER_ROLE);
break;
// User ID.
case 6:
// User ID/Page.
case 7:
drupal_set_header("Cache-Control: {$user_cache_control_header}, max-age={$max_age}");
drupal_set_header("X-BLOCK-CACHE: " . BLOCK_CACHE_PER_USER);
break;
}
}
/**
* 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) {
require_once drupal_get_path('module', 'esi') . '/esi.theme.inc';
// Bail out if it's not handled by ESI.
if (empty($pane->cache['method']) || $pane->cache['method'] != 'esi' || !variable_get('esi_mode', ESI_MODE) || !is_object($content) || isset($content->content) && esi_theme_is_esied($content->content)) {
return;
}
// Code for external ESI blocks
if ($pane->cache['settings']['external'] === 1) {
$content->content = theme('esi_tag', 'external', $pane);
}
else {
$content->content = theme('esi_tag', 'panel', $pane);
}
if (!isset($content->module)) {
$content->module = $pane->type;
}
if (!isset($content->delta)) {
$content->delta = $pane->subtype;
}
return;
}
/**
* 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($bid, $page = NULL, $task_name = NULL, $context_string = NULL) {
// Expect the bid format to be theme:display_id:pane_id
if (substr_count($bid, ':') !== 2) {
return FALSE;
}
list($passed_theme, $display_id, $pane_id) = explode(':', $bid);
global $custom_theme, $theme_key, $user;
// Save this page's internal URL.
$last_arg = explode('/', $_GET['q']);
$last_arg = array_pop($last_arg);
// Set context.
if (isset($page) && strpos($page, 'CACHE=') !== 0) {
$q = base64_decode($page);
if ($q == "") {
$q = variable_get('site_frontpage', 'node');
}
$_GET['q'] = $q;
}
// theme set-up.
// we need to do this manually, because output is echo'd instead of returned.
$custom_theme = $passed_theme;
$theme_key = $passed_theme;
init_theme();
$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.
$config = $display->content[$pane_id]->cache;
unset($display->content[$pane_id]->cache);
// Load in defaults if missing.
if (!isset($config['settings']['max_age'])) {
$config['settings']['max_age'] = (int) variable_get('esi_panel_default_max_age', ESI_PANEL_DEFAULT_MAX_AGE);
}
if (!isset($config['settings']['scope'])) {
$config['settings']['scope'] = (int) variable_get('esi_panel_default_scope', ESI_PANEL_DEFAULT_SCOPE);
}
$current_user = $user;
// Use user 0 if the scope is global or page.
if ($config['settings']['scope'] == 2 || $config['settings']['scope'] == 3) {
$user = user_load(0);
}
if (!function_exists('panels_get_renderer_handler')) {
require_once drupal_get_path('module', 'panels') . '/includes/plugins.inc';
}
// Use the standard renderer to render the pane.
$renderer = panels_get_renderer_handler('standard', $display);
if (!empty($renderer->display->content[$pane_id]->access['plugins'])) {
$renderer->display->content[$pane_id]->access['plugins'] = array_filter($renderer->display->content[$pane_id]->access['plugins'], "filter_out_path_visibility_access");
}
$renderer
->prepare_panes($renderer->display->content);
$output = '';
if (!empty($renderer->prepared['panes'][$pane_id])) {
$output .= $renderer
->render_pane($renderer->prepared['panes'][$pane_id]);
}
else {
$output .= esi_fast404('panel');
}
esi_add_cache_headers($config['settings']['scope'], $config['settings']['max_age']);
echo $output;
$user = $current_user;
// Cache the ESI output.
esi_set_page_cache($output, $config['settings']['scope'], $last_arg);
exit;
}
function filter_out_path_visibility_access($e) {
return $e['name'] != "path_visibility";
}
/**
* 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_name) {
// 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':
}
}
/**
* Implements hook_context_plugins to define our custom block context plugin
*
* @return array
*/
function esi_context_plugins() {
$plugins = array();
$plugins['context_reaction_esi_block'] = array(
'handler' => array(
'path' => drupal_get_path('module', 'esi') . '/plugins',
'file' => 'context_reaction_esi_block.inc',
'class' => 'context_reaction_esi_block',
'parent' => 'context_reaction_block',
),
);
return $plugins;
}
/**
* Implements hook_context_registry_alter to use our custom block context plugin
* to output ESI tags instead of the block
*
* @param $registry array
*/
function esi_context_registry_alter(&$registry) {
if (isset($registry['reactions']['block'])) {
$registry['reactions']['block']['plugin'] = 'context_reaction_esi_block';
}
}
/**
* Generate a fast 404
*
* @return
* Blank page.
*/
function esi_fast404($msg = '') {
global $base_path;
if (!headers_sent()) {
drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
drupal_set_header('X-ESI: Failed To Render. ' . $msg);
}
$output = '';
// $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
// $output .= '<html>';
// $output .= '<head><title>404 Not Found</title></head>';
// $output .= '<body><h1>Not Found</h1>';
// $output .= '<p>The requested URL was not found on this server.</p>';
// $output .= '<p><a href="' . $base_path . '">Home</a></p>';
$output .= '<!-- esi_fast404 -->';
// $output .= '</body></html>';
return $output;
}
function esi_set_page_cache($data, $scope, $last_arg) {
global $base_root;
if (!variable_get('esi_page_cache', ESI_PAGE_CACHE)) {
return FALSE;
}
if (!variable_get('esi_mode', ESI_MODE)) {
return FALSE;
}
require_once drupal_get_path('module', 'esi') . '/esi.inc';
switch ($scope) {
// Disabled or not cached.
case 0:
case 1:
return FALSE;
break;
// Global or page.
case 2:
case 3:
break;
// User level.
case 4:
case 5:
case 6:
case 7:
// Cache only if the last argument of the URL contains CACHE=*.
if (strpos($last_arg, 'CACHE=') !== 0) {
return FALSE;
}
break;
}
if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
$data = gzencode($data, 9, FORCE_GZIP);
}
if (_esi_get_drupal_distribution() !== 'drupal') {
$cache = (object) array(
'cid' => 'esi:' . $base_root . request_uri(),
'data' => $data,
'expire' => CACHE_TEMPORARY,
'created' => $_SERVER['REQUEST_TIME'],
'headers' => array(),
);
// Restore preferred header names based on the lower-case names returned
// by drupal_get_header().
$header_names = _drupal_set_preferred_header_name();
foreach (drupal_get_header() as $name_lower => $value) {
$cache->headers[$header_names[$name_lower]] = $value;
}
return cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire, serialize($cache->headers));
}
else {
return cache_set('esi:' . $base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
}
}
function esi_get_page_cache() {
global $base_root;
if (!variable_get('esi_page_cache', ESI_PAGE_CACHE)) {
return NULL;
}
if (!variable_get('esi_mode', ESI_MODE)) {
return NULL;
}
$cid = 'esi:' . $base_root . request_uri();
// Only try the cache for these paths.
if (strpos($cid, '/esi/block/') === FALSE && strpos($cid, '/esi/panels_pane/') === FALSE) {
return NULL;
}
// See if this is cached.
$cache = cache_get($cid, 'cache_page');
if (empty($cache)) {
header('X-Drupal-ESI-Cache: MISS');
return NULL;
}
// Unserialize headers if they are serialized.
$headers = @unserialize($cache->headers);
if ($headers === FALSE && $cache->headers !== 'b:0;') {
$headers = $cache->headers;
}
if (!is_array($headers)) {
$headers = explode("\n", $headers);
}
// Parse headers.
$max_age = -1;
foreach ($headers as $key => $header) {
// Get the max age.
if (stripos($header, 'Cache-Control: ') === 0 && stripos($header, 'max-age=') !== FALSE || strcasecmp($key, 'Cache-Control') == 0 && stripos($header, 'max-age=') !== FALSE) {
$max_age = (int) substr($header, strpos($header, 'max-age=') + 8);
}
// Set the server protocol.
if (strpos($header, ':status: ') === 0) {
$header = str_replace(':status:', $_SERVER['SERVER_PROTOCOL'], $header);
}
if (strcasecmp($key, ':status') == 0) {
$headers[$_SERVER['SERVER_PROTOCOL']] = $header;
unset($headers[$key]);
}
}
// Check the max age.
$time = (int) isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
if (empty($max_age) || $time - (int) $cache->created > $max_age) {
header('X-Drupal-ESI-Cache: EXPIRED' . $max_age);
return NULL;
}
// Deal with compression.
$page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
$return_compressed = variable_get('page_compression', TRUE) && extension_loaded('zlib') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
$headers[] = 'Etag: "' . $cache->created . '-' . intval($return_compressed) . '"';
if ($page_compression) {
header('Vary: Accept-Encoding', FALSE);
// If page_compression is enabled, the cache contains gzipped data.
if ($return_compressed) {
ini_set('zlib.output_compression', '0');
$headers[] = 'Content-Encoding: gzip';
}
else {
// The client does not support compression, so unzip the data in the
// cache. Strip the gzip header and run uncompress.
$cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
$headers[] = 'X-PF-Uncompressing: 1';
}
}
// Send contents if everything works out.
$headers[] = 'X-Drupal-ESI-Cache: HIT';
foreach ($headers as $key => $header) {
if (is_numeric($key)) {
header($header);
}
else {
header($key . ': ' . $header);
}
}
return $cache->data;
}
function esi_boot() {
if ($cache = esi_get_page_cache()) {
echo $cache;
exit;
}
}
Functions
Name | Description |
---|---|
esi_add_cache_headers | |
esi_admin_menu | Implementation of hook_admin_menu(). |
esi_admin_menu_output_alter | Implementation of hook_admin_menu_output_alter(). |
esi_boot | |
esi_context_plugins | Implements hook_context_plugins to define our custom block context plugin |
esi_context_registry_alter | Implements hook_context_registry_alter to use our custom block context plugin to output ESI tags instead of the block |
esi_cron | Implementation of hook_cron(). Every interval, rotate the seed (used to generate the role-cookie). (Each rotation will invalidate the varnish-cache for cached pre-role blocks). |
esi_ctools_plugin_directory | Implementation of hook_ctools_plugin_directory(). |
esi_fast404 | Generate a fast 404 |
esi_form_block_admin_configure_alter | Implementation of hook_form_FORM_ID_alter(). for block_admin_configure Add ESI-configuration options to the block-config pages. |
esi_get_page_cache | |
esi_init | Implementation of hook_init(). |
esi_menu | Implementation of hook_menu(). Define a menu-handler. |
esi_panels_pane_content_alter | Implementation of hook_pane_content_alter(). |
esi_set_page_cache | |
esi_theme | Implementation of hook_theme(). |
esi_theme_is_esied | See if the block/pane has been esi-ed. |
esi_theme_registry_alter | Implementation of hook_theme_registry_alter(). Override theme('blocks') to apply our ESI handler. |
esi_user | Implementation of hook_user(). For maximum cache-efficiency, the proxy must be able to identify the roles held by a user. A cookie is used which provides a consistent hash for all users who share the same roles. For security, the hash uses a random… |
esi__block_config_save | Form-submit handler for ESI settings in block-config |
esi__block_handler | Menu handler for ESIs |
esi__panels_get_task_context | 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. |
esi__panel_pane_handler | Menu handler to serve individual panel-panes via ESI. |
filter_out_path_visibility_access | |
page_manager_esi_get_context_arguments | Implementation of hook_esi_get_context, provided for page_manager. |