- Patch #80951 by killes, yched et al: block caching.
parent
e9c36f9697
commit
74292cd062
|
@ -54,6 +54,7 @@ Drupal 6.0, xxxx-xx-xx (development version)
|
|||
* Made it easier to conditionally load include files.
|
||||
* Added a JavaScript aggregator and compressor.
|
||||
* Made Drupal work correctly when running behind a reverse proxy like Squid or Pound.
|
||||
* Added block-level caching, improving performance for logged-in users.
|
||||
- File handling improvements:
|
||||
* Entries in the files table are now keyed to a user, and not a node.
|
||||
* Added re-usable validation functions to check for uploaded file sizes, extensions, and image resolution.
|
||||
|
|
|
@ -114,7 +114,7 @@ function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $he
|
|||
/**
|
||||
*
|
||||
* Expire data from the cache. If called without arguments, expirable
|
||||
* entries will be cleared from the cache_page table.
|
||||
* entries will be cleared from the cache_page and cache_block tables.
|
||||
*
|
||||
* @param $cid
|
||||
* If set, the cache ID to delete. Otherwise, all cache entries that can
|
||||
|
@ -134,6 +134,7 @@ function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
|
|||
|
||||
if (!isset($cid) && !isset($table)) {
|
||||
cache_clear_all(NULL, 'cache_page');
|
||||
cache_clear_all(NULL, 'cache_block');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ function block_add_block_form_submit($form, &$form_state) {
|
|||
|
||||
foreach (list_themes() as $key => $theme) {
|
||||
if ($theme->status) {
|
||||
db_query("INSERT INTO {blocks} (visibility, pages, custom, title, module, theme, status, weight, delta) VALUES(%d, '%s', %d, '%s', '%s', '%s', %d, %d, %d)", $form_state['values']['visibility'], trim($form_state['values']['pages']), $form_state['values']['custom'], $form_state['values']['title'], $form_state['values']['module'], $theme->name, 0, 0, $delta);
|
||||
db_query("INSERT INTO {blocks} (visibility, pages, custom, title, module, theme, status, weight, delta, cache) VALUES(%d, '%s', %d, '%s', '%s', '%s', %d, %d, %d)", $form_state['values']['visibility'], trim($form_state['values']['pages']), $form_state['values']['custom'], $form_state['values']['title'], $form_state['values']['module'], $theme->name, 0, 0, $delta, BLOCK_NO_CACHE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,55 @@
|
|||
*/
|
||||
define('BLOCK_REGION_NONE', -1);
|
||||
|
||||
/**
|
||||
* Constants defining cache granularity for blocks.
|
||||
*
|
||||
* Modules specify the caching patterns for their blocks using binary
|
||||
* combinations of these constants in their hook_block(op 'list'):
|
||||
* $block[delta]['cache'] = BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE;
|
||||
* BLOCK_CACHE_PER_ROLE is used as a default when no caching pattern is
|
||||
* specified.
|
||||
*
|
||||
* The block cache is cleared in cache_clear_all(), and uses the same clearing
|
||||
* policy than page cache (node, comment, user, taxonomy added or updated...).
|
||||
* Blocks requiring more fine-grained clearing might consider disabling the
|
||||
* built-in block cache (BLOCK_NO_CACHE) and roll their own.
|
||||
*
|
||||
* Note that user 1 is excluded from block caching.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The block should not get cached. This setting should be used:
|
||||
* - for simple blocks (notably those that do not perform any db query),
|
||||
* where querying the db cache would be more expensive than directly generating
|
||||
* the content.
|
||||
* - for blocks that change too frequently.
|
||||
*/
|
||||
define('BLOCK_NO_CACHE', -1);
|
||||
|
||||
/**
|
||||
* The block can change depending on the roles the user viewing the page belongs to.
|
||||
* This is the default setting, used when the block does not specify anything.
|
||||
*/
|
||||
define('BLOCK_CACHE_PER_ROLE', 0x0001);
|
||||
|
||||
/**
|
||||
* The block can change depending on the user viewing the page.
|
||||
* This setting can be resource-consuming for sites with large number of users,
|
||||
* and thus should only be used when BLOCK_CACHE_PER_ROLE is not sufficient.
|
||||
*/
|
||||
define('BLOCK_CACHE_PER_USER', 0x0002);
|
||||
|
||||
/**
|
||||
* The block can change depending on the page being viewed.
|
||||
*/
|
||||
define('BLOCK_CACHE_PER_PAGE', 0x0004);
|
||||
|
||||
/**
|
||||
* The block is the same for every user on every page where it is visible.
|
||||
*/
|
||||
define('BLOCK_CACHE_GLOBAL', 0x0008);
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
|
@ -187,6 +236,8 @@ function block_block($op = 'list', $delta = 0, $edit = array()) {
|
|||
$result = db_query('SELECT bid, info FROM {boxes} ORDER BY info');
|
||||
while ($block = db_fetch_object($result)) {
|
||||
$blocks[$block->bid]['info'] = $block->info;
|
||||
// Not worth caching.
|
||||
$blocks[$block->bid]['cache'] = BLOCK_NO_CACHE;
|
||||
}
|
||||
return $blocks;
|
||||
|
||||
|
@ -235,6 +286,8 @@ function _block_rehash() {
|
|||
foreach ($module_blocks as $delta => $block) {
|
||||
$block['module'] = $module;
|
||||
$block['delta'] = $delta;
|
||||
// If no cache pattern is specified, we use PER_ROLE as a default.
|
||||
$block['cache'] = isset($block['cache']) ? $block['cache'] : BLOCK_CACHE_PER_ROLE;
|
||||
// If previously written to database, load values.
|
||||
if (!empty($old_blocks[$module][$delta])) {
|
||||
$block['status'] = $old_blocks[$module][$delta]->status;
|
||||
|
@ -271,7 +324,7 @@ function _block_rehash() {
|
|||
'visibility' => NULL,
|
||||
'throttle' => NULL,
|
||||
);
|
||||
db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle, title) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, '%s')", $block['module'], $block['delta'], $theme_key, $block['status'], $block['weight'], $block['region'], $block['visibility'], $block['pages'], $block['custom'], $block['throttle'], $block['title']);
|
||||
db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle, title, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, '%s', %d)", $block['module'], $block['delta'], $theme_key, $block['status'], $block['weight'], $block['region'], $block['visibility'], $block['pages'], $block['custom'], $block['throttle'], $block['title'], $block['cache']);
|
||||
}
|
||||
db_unlock_tables();
|
||||
|
||||
|
@ -429,7 +482,19 @@ function block_list($region) {
|
|||
// Check the current throttle status and see if block should be displayed
|
||||
// based on server load.
|
||||
if (!($block->throttle && (module_invoke('throttle', 'status') > 0))) {
|
||||
$array = module_invoke($block->module, 'block', 'view', $block->delta);
|
||||
// Try fetching the block from cache. Block caching is not compatible with
|
||||
// node_access modules. We also preserve the submission of forms in blocks,
|
||||
// by fetching from cache only if the request method is 'GET'.
|
||||
if (!count(module_implements('node_grants')) && $_SERVER['REQUEST_METHOD'] == 'GET' && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
|
||||
$array = $cache->data;
|
||||
}
|
||||
else {
|
||||
$array = module_invoke($block->module, 'block', 'view', $block->delta);
|
||||
if (isset($cid)) {
|
||||
cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($array) && is_array($array)) {
|
||||
foreach ($array as $k => $v) {
|
||||
$block->$k = $v;
|
||||
|
@ -453,3 +518,55 @@ function block_list($region) {
|
|||
}
|
||||
return $blocks[$region];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble the cache_id to use for a given block.
|
||||
*
|
||||
* The cache_id string reflects the viewing context for the current block
|
||||
* instance, obtained by concatenating the relevant context information
|
||||
* (user, page, ...) according to the block's cache settings (BLOCK_CACHE_*
|
||||
* constants). Two block instances can use the same cached content when
|
||||
* they share the same cache_id.
|
||||
*
|
||||
* Theme and language contexts are automatically differenciated.
|
||||
*
|
||||
* @param $block
|
||||
* @return
|
||||
* The string used as cache_id for the block.
|
||||
*/
|
||||
function _block_get_cache_id($block) {
|
||||
global $theme, $base_root, $user;
|
||||
|
||||
// User 1 being out of the regular 'roles define permissions' schema,
|
||||
// it brings too many chances of having unwanted output get in the cache
|
||||
// and later be served to other users. We therefore exclude user 1 from
|
||||
// block caching.
|
||||
if (variable_get('block_cache', 0) && $block->cache != BLOCK_NO_CACHE && $user->uid != 1) {
|
||||
$cid_parts = array();
|
||||
|
||||
// Start with common sub-patterns: block identification, theme, language.
|
||||
$cid_parts[] = $block->module;
|
||||
$cid_parts[] = $block->delta;
|
||||
$cid_parts[] = $theme;
|
||||
if (module_exists('locale')) {
|
||||
global $language;
|
||||
$cid_parts[] = $language->language;
|
||||
}
|
||||
|
||||
// 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
|
||||
// resource drag for sites with many users, so when a module is being
|
||||
// equivocal, we favor the less expensive 'PER_ROLE' pattern.
|
||||
if ($block->cache & BLOCK_CACHE_PER_ROLE) {
|
||||
$cid_parts[] = 'r.'. implode(',', array_keys($user->roles));
|
||||
}
|
||||
elseif ($block->cache & BLOCK_CACHE_PER_USER) {
|
||||
$cid_parts[] = "u.$user->uid";
|
||||
}
|
||||
|
||||
if ($block->cache & BLOCK_CACHE_PER_PAGE) {
|
||||
$cid_parts[] = $base_root . request_uri();
|
||||
}
|
||||
|
||||
return implode(':', $cid_parts);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,8 @@ function block_schema() {
|
|||
'throttle' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'),
|
||||
'visibility' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'),
|
||||
'pages' => array('type' => 'text', 'not null' => TRUE),
|
||||
'title' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')
|
||||
'title' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''),
|
||||
'cache' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'),
|
||||
),
|
||||
'primary key' => array('bid'),
|
||||
);
|
||||
|
@ -44,6 +45,8 @@ function block_schema() {
|
|||
'primary key' => array('bid'),
|
||||
);
|
||||
|
||||
$schema['cache_block'] = drupal_get_schema_unprocessed('system', 'cache');
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
|
|
@ -164,6 +164,7 @@ function book_block($op = 'list', $delta = 0, $edit = array()) {
|
|||
switch ($op) {
|
||||
case 'list':
|
||||
$block[0]['info'] = t('Book navigation');
|
||||
$block[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
|
||||
return $block;
|
||||
case 'view':
|
||||
if (arg(0) == 'node' && is_numeric(arg(1))) {
|
||||
|
@ -440,7 +441,7 @@ function _book_add_form_elements(&$form, $node) {
|
|||
);
|
||||
$options = array();
|
||||
$nid = isset($node->nid) ? $node->nid : 'new';
|
||||
|
||||
|
||||
if (isset($node->nid) && ($nid == $node->book['original_bid']) && $node->book['has_children'] && (MENU_MAX_DEPTH - 1 - menu_link_children_relative_depth($node->book) == 0)) {
|
||||
// This is the top level node in a maximum depth book and thus cannot be moved.
|
||||
$options[$node->nid] = $node->title;
|
||||
|
|
|
@ -516,6 +516,8 @@ function _locale_batch_import($filepath, &$context) {
|
|||
function locale_block($op = 'list', $delta = 0) {
|
||||
if ($op == 'list') {
|
||||
$block[0]['info'] = t('Language switcher');
|
||||
// Not worth caching.
|
||||
$block[0]['cache'] = BLOCK_NO_CACHE;
|
||||
return $block;
|
||||
}
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ function menu_parent_options($menus, $item) {
|
|||
|
||||
// If the item has children, there is an added limit to the depth of valid parents.
|
||||
$limit = MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? menu_link_children_relative_depth($item) : 0);
|
||||
|
||||
|
||||
foreach ($menus as $menu_name => $title) {
|
||||
$tree = menu_tree_all_data($menu_name, NULL);
|
||||
$options[$menu_name .':0'] = '<'. $title .'>';
|
||||
|
@ -666,6 +666,9 @@ function menu_block($op = 'list', $delta = 0) {
|
|||
foreach ($custom_menus as $name => $title) {
|
||||
// Default "Navigation" block is handled by user.module.
|
||||
$blocks[$name]['info'] = check_plain($title);
|
||||
// Menu blocks can't be cached because each menu item can have
|
||||
// a custom access callback. menu.inc manages its own caching.
|
||||
$blocks[$name]['cache'] = BLOCK_NO_CACHE;
|
||||
}
|
||||
return $blocks;
|
||||
}
|
||||
|
|
|
@ -770,7 +770,7 @@ function node_save(&$node) {
|
|||
// Update the node access table for this node.
|
||||
node_access_acquire_grants($node);
|
||||
|
||||
// Clear the cache so an anonymous poster can see the node being added or updated.
|
||||
// Clear the page and block caches.
|
||||
cache_clear_all();
|
||||
}
|
||||
|
||||
|
@ -1915,6 +1915,8 @@ function node_admin_search() {
|
|||
function node_block($op = 'list', $delta = 0) {
|
||||
if ($op == 'list') {
|
||||
$blocks[0]['info'] = t('Syndicate');
|
||||
// Not worth caching.
|
||||
$blocks[0]['cache'] = BLOCK_NO_CACHE;
|
||||
return $blocks;
|
||||
}
|
||||
else if ($op == 'view') {
|
||||
|
@ -2502,7 +2504,7 @@ function node_delete($nid) {
|
|||
node_invoke($node, 'delete');
|
||||
node_invoke_nodeapi($node, 'delete');
|
||||
|
||||
// Clear the cache so an anonymous poster can see the node being deleted.
|
||||
// Clear the page and block caches.
|
||||
cache_clear_all();
|
||||
|
||||
// Remove this node from the search index if needed.
|
||||
|
|
|
@ -125,7 +125,7 @@ function profile_block($op = 'list', $delta = 0, $edit = array()) {
|
|||
|
||||
if ($op == 'list') {
|
||||
$blocks[0]['info'] = t('Author information');
|
||||
|
||||
$blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
|
||||
return $blocks;
|
||||
}
|
||||
else if ($op == 'configure' && $delta == 0) {
|
||||
|
|
|
@ -144,6 +144,8 @@ function search_perm() {
|
|||
function search_block($op = 'list', $delta = 0) {
|
||||
if ($op == 'list') {
|
||||
$blocks[0]['info'] = t('Search form');
|
||||
// Not worth caching.
|
||||
$blocks[0]['cache'] = BLOCK_NO_CACHE;
|
||||
return $blocks;
|
||||
}
|
||||
else if ($op == 'view' && user_access('search content')) {
|
||||
|
|
|
@ -502,6 +502,8 @@ function statistics_block($op = 'list', $delta = 0, $edit = array()) {
|
|||
case 'list':
|
||||
if (variable_get('statistics_count_content_views', 0)) {
|
||||
$blocks[0]['info'] = t('Popular content');
|
||||
// Too dynamic to cache.
|
||||
$blocks[0]['cache'] = BLOCK_NO_CACHE;
|
||||
return $blocks;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -3480,6 +3480,52 @@ function system_update_6026() {
|
|||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add block cache.
|
||||
*/
|
||||
function system_update_6027() {
|
||||
$ret = array();
|
||||
|
||||
// Create the blocks.cache column.
|
||||
db_add_field($ret, 'blocks', 'cache', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
|
||||
|
||||
// Create the cache_block table.
|
||||
$schema['cache_block'] = array(
|
||||
'fields' => array(
|
||||
'cid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
|
||||
'data' => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
|
||||
'expire' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
|
||||
'headers' => array('type' => 'text', 'not null' => FALSE),
|
||||
'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
|
||||
),
|
||||
'indexes' => array('expire' => array('expire')),
|
||||
'primary key' => array('cid'),
|
||||
);
|
||||
db_create_table($ret, 'cache_block', $schema['cache_block']);
|
||||
|
||||
// Fill in the values for the new 'cache' column,
|
||||
// by refreshing the {blocks} table.
|
||||
global $theme, $custom_theme;
|
||||
$old_theme = $theme;
|
||||
$themes = list_themes();
|
||||
|
||||
$result = db_query("SELECT DISTINCT theme FROM {blocks}");
|
||||
while ($row = db_fetch_array($result)) {
|
||||
if (array_key_exists($row['theme'], $themes)) {
|
||||
// Set up global values so that _blocks_rehash()
|
||||
// operates on the expected theme.
|
||||
$theme = NULL;
|
||||
$custom_theme = $row['theme'];
|
||||
_block_rehash();
|
||||
}
|
||||
}
|
||||
|
||||
$theme = $old_theme;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup updates-5.x-to-6.x"
|
||||
* The next series of updates should start at 7000.
|
||||
|
|
|
@ -683,7 +683,7 @@ function system_performance_settings() {
|
|||
$form['page_cache'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Page cache'),
|
||||
'#description' => t('Enabling the cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by <em>anonymous</em> users. By caching a web page, Drupal does not have to construct the page each time someone wants to view it.'),
|
||||
'#description' => t('Enabling the page cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by <em>anonymous</em> users. By caching a web page, Drupal does not have to construct the page each time someone wants to view it.'),
|
||||
);
|
||||
|
||||
$form['page_cache']['cache'] = array(
|
||||
|
@ -701,7 +701,22 @@ function system_performance_settings() {
|
|||
'#title' => t('Minimum cache lifetime'),
|
||||
'#default_value' => variable_get('cache_lifetime', 0),
|
||||
'#options' => $period,
|
||||
'#description' => t('On high-traffic sites it can become necessary to enforce a minimum cache lifetime. The minimum cache lifetime is the minimum amount of time that will go by before the cache is emptied and recreated. A larger minimum cache lifetime offers better performance, but users will not see new content for a longer period of time.')
|
||||
'#description' => t('On high-traffic sites it can become necessary to enforce a minimum cache lifetime. The minimum cache lifetime is the minimum amount of time that will go by before the cache is emptied and recreated. A larger minimum cache lifetime offers better performance, but users will not see new content for a longer period of time. This setting also affects block caching.')
|
||||
);
|
||||
|
||||
$form['block_cache'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Block cache'),
|
||||
'#description' => t('Enabling the block cache can offer a performance increase for all users by preventing blocks from being reconstructed on every page load. If page cache is also enabled, this performance increase will mainly affect authenticated users.'),
|
||||
);
|
||||
|
||||
$form['block_cache']['block_cache'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Block cache'),
|
||||
'#default_value' => variable_get('block_cache', CACHE_DISABLED),
|
||||
'#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Enabled (recommended)')),
|
||||
'#disabled' => count(module_implements('node_grants')),
|
||||
'#description' => t('Note that block caching is inactive when modules defining content access restrictions are enabled.'),
|
||||
);
|
||||
|
||||
$form['bandwidth_optimizations'] = array(
|
||||
|
@ -1275,8 +1290,8 @@ function system_initialize_theme_blocks($theme) {
|
|||
if (!array_key_exists($block['region'], $regions)) {
|
||||
$block['region'] = system_default_region($theme);
|
||||
}
|
||||
db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d)",
|
||||
$block['module'], $block['delta'], $theme, $block['status'], $block['weight'], $block['region'], $block['visibility'], $block['pages'], $block['custom'], $block['throttle']);
|
||||
db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, %d)",
|
||||
$block['module'], $block['delta'], $theme, $block['status'], $block['weight'], $block['region'], $block['visibility'], $block['pages'], $block['custom'], $block['throttle'], $block['cache']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -627,10 +627,19 @@ function user_block($op = 'list', $delta = 0, $edit = array()) {
|
|||
|
||||
if ($op == 'list') {
|
||||
$blocks[0]['info'] = t('User login');
|
||||
$blocks[1]['info'] = t('Navigation');
|
||||
$blocks[2]['info'] = t('Who\'s new');
|
||||
$blocks[3]['info'] = t('Who\'s online');
|
||||
// Not worth caching.
|
||||
$blocks[0]['cache'] = BLOCK_NO_CACHE;
|
||||
|
||||
$blocks[1]['info'] = t('Navigation');
|
||||
// Menu blocks can't be cached because each menu item can have
|
||||
// a custom access callback. menu.inc manages its own caching.
|
||||
$blocks[1]['cache'] = BLOCK_NO_CACHE;
|
||||
|
||||
$blocks[2]['info'] = t('Who\'s new');
|
||||
|
||||
// Too dynamic to cache.
|
||||
$blocks[3]['info'] = t('Who\'s online');
|
||||
$blocks[3]['cache'] = BLOCK_NO_CACHE;
|
||||
return $blocks;
|
||||
}
|
||||
else if ($op == 'configure' && $delta == 2) {
|
||||
|
|
Loading…
Reference in New Issue