esi_block.module in ESI: Edge Side Includes 7.3
ESI handler for blocks.
Architectural philosophy:
- Remove the existing block as early as possible, to avoid building the contents of the block, only to throw them away. This happens with hook_block_list_alter().
- Use the block's existing cache settings (DRUPAL_CACHE_PER_PAGE, etc) to govern how the ESI URL is constructed, and the cache headers provided when the ESI fragment is delivered.
- Provide *no* theme furniture around the ESI tag. Ensure that the relevant theme furniture is invoked to be delivered with the ESI fragment.
File
modules/esi_block/esi_block.moduleView source
<?php
/**
* @file
* ESI handler for blocks.
*
* Architectural philosophy:
* - Remove the existing block as early as possible, to avoid building the
* contents of the block, only to throw them away. This happens with
* hook_block_list_alter().
* - Use the block's existing cache settings (DRUPAL_CACHE_PER_PAGE, etc) to
* govern how the ESI URL is constructed, and the cache headers provided when
* the ESI fragment is delivered.
* - Provide *no* theme furniture around the ESI tag. Ensure that the relevant
* theme furniture is invoked to be delivered with the ESI fragment.
*/
/**
* Implements hook_esi_component().
*
* @see esi_block_prepare()
* @see esi_block_render()
*/
function esi_block_esi_component_info() {
return array(
'block' => array(
'preprocess' => 'esi_block__esi_block_prepare',
'render' => 'esi_block__esi_block_render',
'flush' => 'esi_block__esi_block_flush',
'file' => 'esi_block.esi.inc',
),
);
}
/**
* Implements hook_page_alter().
*/
function esi_block_page_alter(&$page) {
// The block system hard-codes some aspects of block information
// in _block_get_renderable_array().
// ESI blocks need to:
// - Remove the contextual-links data.
// - Remove the 'block' theme wrapper.
foreach (element_children($page) as $region_key) {
foreach (element_children($page[$region_key]) as $block_key) {
if (isset($page[$region_key][$block_key]['#block']) && is_object($page[$region_key][$block_key]['#block']) && !empty($page[$region_key][$block_key]['#block']->esi_enabled)) {
// Remove contextual-links.
unset($page[$region_key][$block_key]['#contextual_links']);
// Remove the theme wrapper.
unset($page[$region_key][$block_key]['#theme_wrappers']);
}
}
}
}
/**
* Implements hook_block_list_alter().
*
* @see _block_load_blocks()
*/
function esi_block_block_list_alter(&$block_info) {
// Remove the blocks which have been marked as being served via ESI, and
// replace the blocks with the ESI handler.
// Altering the blocks here (rather than in the theme layer) is more
// performant because the overhead of block-generation (for a block which
// won't be rendered) is removed.
foreach ($block_info as $key => $block) {
if ($block->esi_enabled) {
// Preserve the original module-delta combination and pass to ESI as the
// replacement block's new delta.
// The format allows the delta to be gracefully split into the original
// module:delta components but still conforms to the standards which let
// the module:delta be used in constructing function names - for
// altering, themeing, etc.
$new_delta = esi_block__new_delta($block->module, $block->delta);
$block_info[$key]->module = 'esi_block';
$block_info[$key]->delta = $new_delta;
}
}
}
/**
* Implements hook_block_view().
*/
function esi_block_block_view($delta) {
// At this stage, the region where the block is being rendered isn't
// provided. Return an empty content value, and the content will be
// populated once hook_block_view_alter() is invoked.
return array(
'content' => array(
'#markup' => '',
),
);
}
/**
* Implements hook_block_view_alter().
*/
function esi_block_block_view_alter(&$data, $block) {
// The region isn't known in hook_block_view(). This is the first hook where
// the region is provided.
if ($block->module == 'esi_block') {
// Build a URL which contains all the necessary data.
$url = url(esi_block_url($block), array(
'absolute' => TRUE,
));
$data['content'] = array(
'#type' => 'esi',
'#url' => $url,
);
}
}
/**
* Implements hook_form_FORM_ID_alter().
* for block_admin_configure
* Add ESI-configuration options to the block-config pages.
*/
function esi_block_form_block_admin_configure_alter(&$form, $form_state) {
$module = $form['module']['#value'];
$delta = $form['delta']['#value'];
$block = block_load($module, $delta);
$element['esi_config'] = array(
'#title' => t('<abbr title="Edge Side Includes">ESI</abbr> settings'),
'#type' => 'fieldset',
'#description' => t('Control how this block is cached on an ESI-enabled reverse proxy.'),
'#collapsible' => TRUE,
// Only open the block is ESI is enabled.
'#collapsed' => empty($block->esi_enabled),
);
$element['esi_config']['esi_enabled'] = array(
'#title' => t('Enable ESI'),
'#type' => 'checkbox',
'#default_value' => isset($block->esi_enabled) ? $block->esi_enabled : 0,
);
$element['esi_config']['esi_ttl'] = array(
'#title' => t('Cache Maximum Age (TTL)'),
'#type' => 'select',
'#options' => isset($block->esi_ttl) ? esi_max_age_options($block->esi_ttl) : esi_max_age_options(),
'#default_value' => isset($block->esi_ttl) ? is_null($block->esi_ttl) ? ESI_DEFAULT_TTL : $block->esi_ttl : ESI_DEFAULT_TTL,
'#description' => t('The maximum time (in seconds) that proxies or external caches should cache this individual block.'),
);
// Target the "visibility_title" vertical-tabs, and inject the form elements
// before them.
if ($index = array_search('visibility_title', array_keys($form))) {
$form = array_slice($form, 0, $index) + $element + array_slice($form, $index);
}
else {
$form += $element;
}
// The form only saves particular fields. Add a submit handler to save the
// ESI data.
array_unshift($form['#submit'], 'esi_block_block_admin_configure_submit');
}
/**
* Submit handler for the block_admin_configure form.
*/
function esi_block_block_admin_configure_submit($form, &$form_state) {
// The regular block-configuration form only saves particular fields. This
// submit handler stores the ESI-specific data.
esi_block__set_esi_settings($form_state['values']['module'], $form_state['values']['delta'], (bool) $form_state['values']['esi_enabled'], (int) $form_state['values']['esi_ttl']);
}
/**
* Configure the ESI settings for a particular block.
*
* @param String $module
* Module implementing the block.
* @param String $delta
* The block's delta.
* @param Boolean $esi_enabled
* Set to TRUE if this block should be served by ESI.
* @param Int $esi_ttl
* Time-to-live: cache lifetime for this block when served by ESI.
*/
function esi_block__set_esi_settings($module, $delta, $esi_enabled, $esi_ttl) {
$transaction = db_transaction();
try {
db_update('block')
->fields(array(
'esi_enabled' => (int) $esi_enabled,
'esi_ttl' => (int) $esi_ttl,
))
->condition('module', $module)
->condition('delta', $delta)
->execute();
} catch (Exception $e) {
$transaction
->rollback();
watchdog_exception('ESI block', $e);
throw $e;
}
}
/**
* Convert $module and $delta to and from the new encoded $delta.
*
* @example
* $new_delta = esi_block__new_delta($module, $delta);
* @example
* list($module, $delta) = esi_block__new_delta($new_delta);
*/
function esi_block__new_delta() {
$args = func_get_args();
// Pass 2 arguments to convert from $module, $delta to the new $delta.
if (count($args) == 2) {
$module = $args[0];
$delta = $args[1];
$new_delta = 's' . strlen($module) . '_' . $module . '_' . $delta;
return $new_delta;
}
else {
if (count($args) == 1) {
$new_delta = $args[0];
// Strip the prefixed 's' from the size value.
list($size, $delta) = explode('_', substr($new_delta, 1), 2);
$module = substr($delta, 0, $size);
$delta = substr($delta, $size + 1);
return array(
$module,
$delta,
);
}
}
}
/**
* Build the URL to use for this ESI component. The URL must contain all the
* relevant information required to restore the original context of this block.
*
* @param Object $block.
* A populated block object (as made available in hook_block_view_alter())
* containing as a minimum the keys:
* - cache
* - module
* - delta
* - region
* - theme
*
* @return String
* The internal URL. Generate a fully-qualified path by running through url().
*/
function esi_block_url($block) {
// ESI 6.x-1.x and 6.x-2.x used the URL patterns:
// Default: esi/block/theme:region:module:delta
// Cache-per-page: esi/block/theme:region:module:delta/[base64($_GET['q'])]
// Cache-per-role: esi/block/theme:region:module:delta/CACHE=ROLE
// Cache-per-role DI: esi/block/theme:region:module:delta/CACHE=[rolehash]
// Cache-per-page-role: esi/block/theme:region:module:delta/[base64($_GET['q'])]/CACHE=ROLE
// Cache-per-page-role DI: esi/block/theme:region:module:delta/[base64($_GET['q'])]/CACHE=[rolehash]
// Cache-per-user: esi/block/theme:region:module:delta/CACHE=USER
// Cache-per-user DI: esi/block/theme:region:module:delta/CACHE=[userhash]
// Cache-per-user-page: esi/block/theme:region:module:delta/[base64($_GET['q'])]/CACHE=USER
// Cache-per-user-page DI: esi/block/theme:region:module:delta/[base64($_GET['q'])]/CACHE=[userhash]
// Get the original module/delta.
list($module, $delta) = esi_block__new_delta($block->delta);
// Build the "theme:region:module:delta" key.
$component_key = implode(':', array(
$block->theme,
$block->region,
$module,
$delta,
));
$url = "esi/block/{$component_key}";
// Use the $block->cache parameter (as defined in the database) to determine
// the caching rules for this block (per-user, per-role, etc).
// The cache configuration is defined in hook_block_info(), and may be
// altered through hook_block_info_alter(). The alter hook is the correct
// method to specify a custom cache configuration which is different from
// that defined in the original hook_block_info().
// If the block changes per page, encode the page URL in the ESI URL.
if ($block->cache & DRUPAL_CACHE_PER_PAGE) {
$url .= '/' . base64_encode($_GET['q']);
}
if ($block->cache != DRUPAL_NO_CACHE) {
// DRUPAL_CACHE_PER_ROLE and DRUPAL_CACHE_PER_USER are mutually exclusive.
// DRUPAL_CACHE_PER_USER takes precedence.
// Do not inject the actual roles or user data here; this string must not
// contain any personalisation, if the current page is to be cacheable.
if ($block->cache & DRUPAL_CACHE_PER_USER) {
$url .= '/CACHE=USER';
}
elseif ($block->cache & DRUPAL_CACHE_PER_ROLE) {
$url .= '/CACHE=ROLE';
}
}
// Allow other modules to alter the ESI URL (or respond to it).
// @see hook_esi_block_url_alter().
drupal_alter('esi_block_url', $url);
return $url;
}
Functions
Name | Description |
---|---|
esi_block_block_admin_configure_submit | Submit handler for the block_admin_configure form. |
esi_block_block_list_alter | Implements hook_block_list_alter(). |
esi_block_block_view | Implements hook_block_view(). |
esi_block_block_view_alter | Implements hook_block_view_alter(). |
esi_block_esi_component_info | Implements hook_esi_component(). |
esi_block_form_block_admin_configure_alter | Implements hook_form_FORM_ID_alter(). for block_admin_configure Add ESI-configuration options to the block-config pages. |
esi_block_page_alter | Implements hook_page_alter(). |
esi_block_url | Build the URL to use for this ESI component. The URL must contain all the relevant information required to restore the original context of this block. |
esi_block__new_delta | Convert $module and $delta to and from the new encoded $delta. |
esi_block__set_esi_settings | Configure the ESI settings for a particular block. |