### Eclipse Workspace Patch 1.0
#P Test Drupal 6 - BLOCK_CACHE
Index: modules/block/block.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v
retrieving revision 1.5
diff -u -r1.5 block.admin.inc
--- modules/block/block.admin.inc	12 Aug 2007 15:55:35 -0000	1.5
+++ modules/block/block.admin.inc	16 Aug 2007 14:45:18 -0000
@@ -262,7 +262,7 @@
 
   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);
     }
   }
 
Index: modules/block/block.schema
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.schema,v
retrieving revision 1.1
diff -u -r1.1 block.schema
--- modules/block/block.schema	25 May 2007 12:46:43 -0000	1.1
+++ modules/block/block.schema	16 Aug 2007 14:45:18 -0000
@@ -15,7 +15,8 @@
       '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 @@
     'primary key' => array('bid'),
   );
 
+  $schema['cache_block'] = drupal_get_schema_unprocessed('system', 'cache');
+
   return $schema;
 }
 
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.274
diff -u -r1.274 block.module
--- modules/block/block.module	24 Jul 2007 18:17:30 -0000	1.274
+++ modules/block/block.module	16 Aug 2007 14:45:18 -0000
@@ -13,6 +13,63 @@
 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);
+
+/**
+* The block uses custom criteria to determine how it gets cached.
+* hook_block($op = 'cache_id') should then return a string to use as a
+* refining context for the cache_id.
+* @see _block_get_cache_id.
+*/
+define('BLOCK_CACHE_CUSTOM', 0x0010);
+
+/**
  * Implementation of hook_help().
  */
 function block_help($path, $arg) {
@@ -187,6 +244,8 @@
       $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 +294,8 @@
       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 +332,7 @@
       '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 +490,19 @@
         // 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 +526,60 @@
   }
   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;
+    }
+
+    // Let the module define specific cache granularity.
+    if ($block->cache & BLOCK_CACHE_CUSTOM && $custom = module_invoke($block->module, 'block', 'cache_id', $block->delta)) {
+      $cid_parts[] = $custom;
+    }
+
+    // '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);
+  }
+}
\ No newline at end of file
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.870
diff -u -r1.870 node.module
--- modules/node/node.module	12 Aug 2007 16:12:00 -0000	1.870
+++ modules/node/node.module	16 Aug 2007 14:45:22 -0000
@@ -770,7 +770,7 @@
   // 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_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 @@
     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.
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.434
diff -u -r1.434 book.module
--- modules/book/book.module	16 Aug 2007 12:47:34 -0000	1.434
+++ modules/book/book.module	16 Aug 2007 14:45:19 -0000
@@ -164,6 +164,7 @@
   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))) {
Index: modules/statistics/statistics.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/statistics/statistics.module,v
retrieving revision 1.264
diff -u -r1.264 statistics.module
--- modules/statistics/statistics.module	12 Aug 2007 15:55:36 -0000	1.264
+++ modules/statistics/statistics.module	16 Aug 2007 14:45:24 -0000
@@ -502,6 +502,8 @@
     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;
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.134
diff -u -r1.134 menu.module
--- modules/menu/menu.module	16 Aug 2007 12:47:34 -0000	1.134
+++ modules/menu/menu.module	16 Aug 2007 14:45:20 -0000
@@ -666,6 +666,9 @@
     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;
   }
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.187
diff -u -r1.187 locale.module
--- modules/locale/locale.module	3 Jul 2007 16:27:51 -0000	1.187
+++ modules/locale/locale.module	16 Aug 2007 14:45:19 -0000
@@ -516,6 +516,8 @@
 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;
   }
 
Index: includes/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.12
diff -u -r1.12 cache.inc
--- includes/cache.inc	25 May 2007 21:01:30 -0000	1.12
+++ includes/cache.inc	16 Aug 2007 14:45:17 -0000
@@ -114,7 +114,7 @@
 /**
  *
  * 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 @@
 
   if (!isset($cid) && !isset($table)) {
     cache_clear_all(NULL, 'cache_page');
+    cache_clear_all(NULL, 'cache_block');
     return;
   }
 
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.517
diff -u -r1.517 system.module
--- modules/system/system.module	12 Aug 2007 15:55:36 -0000	1.517
+++ modules/system/system.module	16 Aug 2007 14:45:29 -0000
@@ -683,7 +683,7 @@
   $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 @@
     '#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 @@
       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']);
     }
   }
 }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.135
diff -u -r1.135 system.install
--- modules/system/system.install	12 Aug 2007 16:12:00 -0000	1.135
+++ modules/system/system.install	16 Aug 2007 14:45:26 -0000
@@ -3480,6 +3480,52 @@
 }
 
 /**
+ * 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.
  */
Index: modules/search/search.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.module,v
retrieving revision 1.229
diff -u -r1.229 search.module
--- modules/search/search.module	8 Jul 2007 12:18:02 -0000	1.229
+++ modules/search/search.module	16 Aug 2007 14:45:24 -0000
@@ -144,6 +144,8 @@
 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')) {
Index: CHANGELOG.txt
===================================================================
RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v
retrieving revision 1.218
diff -u -r1.218 CHANGELOG.txt
--- CHANGELOG.txt	16 Jul 2007 09:29:43 -0000	1.218
+++ CHANGELOG.txt	16 Aug 2007 14:45:17 -0000
@@ -54,6 +54,7 @@
     * 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.
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.832
diff -u -r1.832 user.module
--- modules/user/user.module	16 Aug 2007 13:07:31 -0000	1.832
+++ modules/user/user.module	16 Aug 2007 14:45:31 -0000
@@ -627,10 +627,19 @@
 
   if ($op == 'list') {
      $blocks[0]['info'] = t('User login');
+     // 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');
-     $blocks[3]['info'] = t('Who\'s online');
 
+     // 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) {
Index: modules/profile/profile.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/profile/profile.module,v
retrieving revision 1.215
diff -u -r1.215 profile.module
--- modules/profile/profile.module	2 Aug 2007 10:36:42 -0000	1.215
+++ modules/profile/profile.module	16 Aug 2007 14:45:23 -0000
@@ -125,7 +125,7 @@
 
   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) {
