You are here

blockcache_alter.module in Block Cache Alter 6

Same filename and directory in other branches
  1. 7 blockcache_alter.module

Block Cache alter.

File

blockcache_alter.module
View source
<?php

/**
 * @file
 * Block Cache alter.
 */

/**
 * Implementation of hook_menu().
 */
function blockcache_alter_menu() {
  $items['admin/settings/blockcache_alter'] = array(
    'title' => 'Block cache alter',
    'description' => 'Debug settings for Blockcache Alter',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'blockcache_alter_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
  );
  return $items;
}

/**
 * Blockcache alter rehash function.
 *
 * This function is called during the block list administration screen
 * and during hook_flush_caches() so the cache settings are updated.
 */
function blockcache_alter_rehash() {
  $caches = db_query('SELECT * FROM {blockcache_alter}');
  while ($block = db_fetch_object($caches)) {
    db_query("UPDATE {blocks} SET cache = %d WHERE module = '%s' AND delta = '%s'", $block->cache, $block->module, $block->delta);
  }
}

/**
 * Implementation of hook_flush_caches().
 */
function blockcache_alter_flush_caches() {
  blockcache_alter_rehash();
}

/**
 * Menu callback, settings page for blockcache alter
 */
function blockcache_alter_admin_settings() {
  $status = _blockcache_alter_core_patch();
  $form['bca_corepatch'] = array(
    '#type' => 'checkbox',
    '#title' => t('Core patch ?'),
    '#description' => t('Check this box if you applied one of the 2 core patches that comes with this module. This will extend the caching settings fieldset on the block configuration page. Note: if you did not apply the patch but check the box, the additional functionality simply won\'t work at all.<br /><strong>Current status: you have @status applied a blockcache alter patch.</strong>', array(
      '@status' => $status,
    )),
    '#default_value' => variable_get('bca_corepatch', 0),
  );
  $form['bca_debug'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show debug information ?'),
    '#description' => t('Debug info: shows messages when each block is refreshed.'),
    '#default_value' => variable_get('bca_debug', 0),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_form_alter().
 */
function blockcache_alter_form_alter(&$form, $form_state, $form_id) {

  // Rehash on the block admin screen.
  if ($form_id == 'block_admin_display_form') {
    blockcache_alter_rehash();
  }

  // Add caching fieldset to the block configure page.
  if ($form_id == 'block_admin_configure') {

    // Add some funky hide and show javascript.
    drupal_add_js(drupal_get_path('module', 'blockcache_alter') . '/blockcache_alter.js', 'module');

    // Core patch applied or not.
    $core_patch = variable_get('bca_corepatch', 0);

    // Blockcache options.
    $block_cache_options = array(
      BLOCK_NO_CACHE => t('Do not cache'),
      BLOCK_CACHE_GLOBAL => t('Cache once for everything (global)'),
      BLOCK_CACHE_PER_PAGE => t('Per page'),
      BLOCK_CACHE_PER_ROLE => t('Per role'),
      BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE => t('Per role per page'),
      BLOCK_CACHE_PER_USER => t('Per user'),
      BLOCK_CACHE_PER_USER | BLOCK_CACHE_PER_PAGE => t('Per user per page'),
    );

    // Cache clear actions.
    $cache_clear_actions = array(
      '1' => t('Clear caching for this block only'),
      '2' => t('Clear all cache (block and page)'),
    );

    // Retrieve current cache setting for this block.
    $default_value = db_result(db_query("SELECT cache FROM {blockcache_alter} WHERE module = '%s' AND delta = '%s'", $form['module']['#value'], $form['delta']['#value']));

    // Blockcache fieldset.
    $form['cache'] = array(
      '#type' => 'fieldset',
      '#title' => t('Cache settings'),
      '#weight' => 1,
      '#collapsible' => TRUE,
    );

    // Cache setting.
    $form['cache']['cache_block'] = array(
      '#type' => 'select',
      '#title' => t('Cache setting'),
      '#description' => t('Select the appropriate cache setting for this block.'),
      '#options' => $block_cache_options,
      '#default_value' => $default_value,
      '#attributes' => array(
        'onChange' => 'javascript:BlockCacheAlterCheck(); return false;',
      ),
    );

    // Cache clearing option after saving this block.
    $display = $default_value == BLOCK_NO_CACHE ? 'none' : 'block';
    $form['cache']['cache_block_clear'] = array(
      '#type' => 'radios',
      '#title' => t('Clear cache'),
      '#prefix' => '<div id="blockcache_alter_wrapper" style="display: ' . $display . ';">',
      '#description' => t('Select the appropriate cache clearing action after saving this block.'),
      '#options' => $cache_clear_actions,
      '#default_value' => 1,
    );
    if ($core_patch != TRUE) {
      $form['cache']['cache_block_clear']['#suffix'] = '</div>';
    }

    // Show options underneath onl if core patch is applied.
    if ($core_patch == TRUE) {

      // Blockcache fieldset.
      $form['cache']['option_1'] = array(
        '#type' => 'fieldset',
        '#title' => t('Cache refresh option 1: Cache lifetime'),
        '#description' => t('Set a minimum cache lifetime (in seconds). Leave 0 or empty to use the second option.'),
      );

      // Blockcache lifetime.
      $form['cache']['option_1']['bc_life'] = array(
        '#type' => 'textfield',
        '#title' => t('Cache lifetime'),
        '#size' => 10,
        '#default_value' => variable_get('bc_life_' . $form['module']['#value'] . '_' . $form['delta']['#value'], ''),
      );

      // Blockcache fieldset.
      $form['cache']['option_2'] = array(
        '#type' => 'fieldset',
        '#title' => t('Cache refresh option 2: Drupal actions'),
        '#description' => t('Refresh on certain actions in the Drupal system. When choosing node or comment action, you also need to specify on which node types it should refresh. Note that clicking any of these checkboxes will not have any effect if a minimum lifetime is set in option 1.'),
      );

      // Blockcache refresh.
      $options = array(
        'node' => t('A node is added/updated/deleted'),
        'comment' => t('A comment is added/updated/deleted'),
        'user' => t('A user is added/updated/deleted'),
        'login' => t('A user logs in our out'),
      );

      // Nodequeue support.
      if (module_exists('nodequeue') && $form['module']['#value'] == 'views' && strstr($form['delta']['#value'], 'nodequeue') !== FALSE) {
        $options['nodequeue'] = t('A node is added or removed from a nodequeue');
      }
      $form['cache']['option_2']['bc_refresh'] = array(
        '#prefix' => '<div style="float: left;">',
        '#suffix' => '</div>',
        '#type' => 'checkboxes',
        '#title' => t('Refresh when'),
        '#options' => $options,
        '#default_value' => variable_get('bc_refresh_' . $form['module']['#value'] . '_' . $form['delta']['#value'], array()),
      );

      // Associate with node types.
      $node_types = array();
      $types = node_get_types('types');
      foreach ($types as $type) {
        $node_types[$type->type] = $type->name;
      }
      $form['cache']['option_2']['bc_relate'] = array(
        '#prefix' => '<div style="float: left; margin-left: 15px;">',
        '#suffix' => '</div><div style="clear: both;"></div>',
        '#type' => 'checkboxes',
        '#title' => t('Relation'),
        '#options' => $node_types,
        '#default_value' => variable_get('bc_relate_' . $form['module']['#value'] . '_' . $form['delta']['#value'], array()),
      );

      // End div.
      $form['cache']['enddiv'] = array(
        '#type' => 'hidden',
        '#suffix' => '</div>',
      );
    }

    // Add our own submit handler and remove the default submit handler.
    unset($form['#submit'][0]);
    $form['submit']['#weight'] = 2;
    $form['#submit'][] = 'blockcache_alter_save_settings';
  }

  // Alter the performance settings page: remove the #disabled key from the block_cache form completely
  // and add a warning message instead to warn the user that block caching won't work when one
  // at least on module implements node_grants and blockcache_alter_no_node_grants is not applied.
  // Note, again, we don't check which patch is applied, that's up to the developer.
  if ($form_id == 'system_performance_settings') {
    $node_grants = $form['block_cache']['block_cache']['#disabled'];
    $form['block_cache']['block_cache']['#disabled'] = FALSE;
    if ($node_grants == TRUE) {
      $form['block_cache']['block_cache']['#description'] = t('There are modules implementing node grants. If you want block caching to work, you need to apply the "blockcache_alter_no_node_grants.patch"');
    }
    else {
      $form['block_cache']['block_cache']['#description'] = '';
    }
  }
}

/**
 * Submit callback. Saves block configuration and cache settings per block.
 * This module takes over from block_admin_configure_submit() so we can
 * decide what to clear from cache.
 */
function blockcache_alter_save_settings($form, &$form_state) {
  if (!form_get_errors()) {
    db_query("UPDATE {blocks} SET visibility = %d, pages = '%s', custom = %d, title = '%s' WHERE module = '%s' AND delta = '%s'", $form_state['values']['visibility'], trim($form_state['values']['pages']), $form_state['values']['custom'], $form_state['values']['title'], $form_state['values']['module'], $form_state['values']['delta']);
    db_query("DELETE FROM {blocks_roles} WHERE module = '%s' AND delta = '%s'", $form_state['values']['module'], $form_state['values']['delta']);
    foreach (array_filter($form_state['values']['roles']) as $rid) {
      db_query("INSERT INTO {blocks_roles} (rid, module, delta) VALUES (%d, '%s', '%s')", $rid, $form_state['values']['module'], $form_state['values']['delta']);
    }
    module_invoke($form_state['values']['module'], 'block', 'save', $form_state['values']['delta'], $form_state['values']);
    drupal_set_message(t('The block configuration has been saved.'));

    // Save block cache settings.
    db_query("DELETE FROM {blockcache_alter} WHERE module = '%s' AND delta = '%s'", $form_state['values']['module'], $form_state['values']['delta']);
    db_query("INSERT INTO {blockcache_alter} (module, delta, cache) VALUES ('%s', '%s', %d)", $form_state['values']['module'], $form_state['values']['delta'], $form_state['values']['cache_block']);

    // Core patch applied or not.
    $core_patch = variable_get('bca_corepatch', 0);

    // Cache clearing
    switch ($form_state['values']['cache_block_clear']) {
      case '1':
        cache_clear_all($form_state['values']['module'] . ':' . $form_state['values']['delta'], 'cache_block', TRUE);
        break;
      case '2':
        cache_clear_all();
        break;
    }

    // Store extra variables if core patch is applied.
    if ($core_patch == TRUE) {

      // Remove old variables to avoid clutter in the variable table.
      db_query("DELETE FROM {variable} WHERE name IN ('%s', '%s', '%s')", 'bc_life_' . $form['module']['#value'] . '_' . $form['delta']['#value'], 'bc_refresh_' . $form['module']['#value'] . '_' . $form['delta']['#value'], 'bc_relate_' . $form['module']['#value'] . '_' . $form['delta']['#value']);

      // Remember block expire time or refresh actions for the future.
      if (!empty($form_state['values']['bc_life'])) {
        variable_set('bc_life_' . $form['module']['#value'] . '_' . $form['delta']['#value'], $form_state['values']['bc_life']);
      }
      else {
        variable_set('bc_refresh_' . $form['module']['#value'] . '_' . $form['delta']['#value'], $form_state['values']['bc_refresh']);
        variable_set('bc_relate_' . $form['module']['#value'] . '_' . $form['delta']['#value'], $form_state['values']['bc_relate']);
      }
    }

    // Redirect.
    $form_state['redirect'] = 'admin/build/block';
    return;
  }
}

/**
 * Helper function to check if a blockcache alter patch is found.
 * Note: it doesn't check which patch is applied.
 */
function _blockcache_alter_core_patch() {
  $status = 'not';
  $block_file = drupal_get_filename('module', 'block');
  $block_contents = file_get_contents($block_file);
  if (strpos($block_contents, 'bca_debug') !== FALSE) {
    $status = '';
  }
  return $status;
}

/**
 * Helper function to check if this block should be refreshed or not.
 *
 * @param stdClass $cache A complete cache object.
 * @param int $time The current timestamp.
 * @return FALSE or TRUE
 */
function _blockcache_alter_check_expire($cache, $time) {
  if ($cache->expire == CACHE_PERMANENT) {
    return TRUE;
  }
  if ($cache->expire > $time) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implementation of hook_nodeapi().
 */
function blockcache_alter_nodeapi($node, $op) {
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
      _blockcache_alter_cleanup('node', $node->type);
      break;
  }
}

/**
 * Implementation of hook_comment().
 */
function blockcache_alter_comment($comment, $op) {
  switch ($op) {
    case 'insert':
      $thiscnode = node_load($comment['nid']);
      $nodetype = $thiscnode->type;
      _blockcache_alter_cleanup('comment', $nodetype);
      break;
    case 'update':
      $thiscnode = node_load($comment['nid']);
      $aabb = $thiscnode->type;
      _blockcache_alter_cleanup('comment', $nodetype);
      break;
    case 'delete':
      $thiscnode = node_load($comment->nid);
      $nodetype = $thiscnode->type;
      _blockcache_alter_cleanup('comment', $nodetype);
      break;
  }
}

/**
 * Implementation of hook_user().
 */
function blockcache_alter_user($op) {
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
      _blockcache_alter_cleanup('user', 'user_actions');
      break;
    case 'login':
    case 'logout':
      _blockcache_alter_cleanup('login', 'user_actions');
      break;
  }
}

/**
 * Cleanup cached blocks if necessary.
 *
 * @param string $type operation type.
 * @param string $relatednodetype Related node type (if available)
 */
function _blockcache_alter_cleanup($type, $relatednodetype = FALSE) {
  $info = array();
  $debug = variable_get('bca_debug', FALSE) && user_access('administer site configuration');
  $modules = variable_get('blockcache_alter_include_modules', array());
  if (!empty($modules)) {
    $placeholders = db_placeholders($modules, 'varchar');
    $query = "SELECT module, delta FROM {blocks} WHERE (status = '1' OR module IN ({$placeholders})) AND cache <> '-1'";
    $result = db_query($query, $modules);
  }
  else {
    $query = "SELECT module, delta FROM {blocks} WHERE status = '1' AND cache <> '-1'";
    $result = db_query($query);
  }
  while ($r = db_fetch_object($result)) {
    if (module_exists($r->module)) {
      $refresh = variable_get('bc_refresh_' . $r->module . '_' . $r->delta, array());
      if (isset($refresh[$type]) && $refresh[$type] === $type) {
        $relate = variable_get('bc_relate_' . $r->module . '_' . $r->delta, array());
        if ($relatednodetype == 'user_actions' || is_array($relate) && $relate[$relatednodetype] === $relatednodetype) {
          cache_clear_all($r->module . ':' . $r->delta, 'cache_block', TRUE);
          if ($debug) {
            $info[] = t('<br /> - module: %mod, delta: %delta', array(
              '%mod' => $r->module,
              '%delta' => $r->delta,
            ));
          }
        }
      }
    }
  }

  // Let other modules cleanup more.
  $hook = 'blockcache_alter_cleanup';
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    $function($type, $relatednodetype, $info);
  }

  // Implode and put together a LIKE query.
  if ($debug && !empty($info)) {
    drupal_set_message(t("Block re-cached: " . implode('&nbsp;', $info)));
  }
}

/**
 * Implementation of hook_nodequeue_add().
 */
function blockcache_alter_nodequeue_add($sqid, $nid) {
  _blockcache_alter_nodequeue_cleanup($sqid);
}

/**
 * Implementation of hook_nodequeue_remove().
 */
function blockcache_alter_nodequeue_remove($sqid, $nid) {
  _blockcache_alter_nodequeue_cleanup($sqid);
}

/**
 * Helper function to cleanup cache for nodequeue block.
 */
function _blockcache_alter_nodequeue_cleanup($sqid) {
  $qid = db_result(db_query("SELECT qid FROM {nodequeue_subqueue} WHERE sqid = %d", $sqid));
  if ($qid !== FALSE) {
    $sqid = $qid;
  }
  $refresh = variable_get('bc_refresh_views_nodequeue_' . $sqid . '-block', array());
  if ($refresh['nodequeue'] == 'nodequeue') {
    cache_clear_all('views:nodequeue_' . $sqid . '-block', 'cache_block', TRUE);
  }
}

/**
 * Implementation of hook_block().
 *
 * Functions underneath will only be called when someone programmaticaly
 * calls a block in code with module_invoke, eg:
 *
 * $block = module_invoke('blockcache_alter', 'block', 'view', 'block,14');
 * print $block['content'];
 *
 * The last parameter should consist of the name of original block and its
 * original delta seperated by comma. All blocks can be cached this way.
 */
function blockcache_alter_block($op, $delta = 0, $edit = array()) {
  switch ($op) {
    case 'view':
      return blockcache_alter_block_view($delta);
  }
}

/**
 * View a cached block using hook_block().
 * If the block is not cached or the cache is stale, get the info and stick it back into the cache.
 */
function blockcache_alter_block_view($delta) {
  $d = explode(",", $delta);
  $module = $d[0];
  $delta = $d[1];
  $cid = _blockcache_alter_get_cache_id($module, $delta);
  if ($cid && ($cache = cache_get($cid, 'cache_block'))) {
    $array = $cache->data;
  }
  else {
    $array = module_invoke($module, 'block', 'view', $delta);
    if (isset($cid)) {
      $blocklife = variable_get('bc_life_' . $module . '_' . $delta, '');
      $blocklife = (int) $blocklife + time();
      cache_set($cid, $array, 'cache_block', $blocklife);
      if ($debug = variable_get('bca_debug', FALSE) && user_access('administer site configuration')) {
        drupal_set_message('block re-cached: ' . $block->title . '_' . $module . '_' . $delta . '_' . $blocklife . '_' . time());
      }
    }
  }
  return $array;
}

/**
 * Get cid for a block.
 */
function _blockcache_alter_get_cache_id($module, $delta) {
  global $theme, $base_root, $user;
  static $block_cache = array();
  if (!isset($block_cache[$module][$delta])) {
    $result = db_query("SELECT module, delta, cache FROM {blocks} WHERE theme = '%s'", $theme);
    while ($row = db_fetch_object($result)) {
      $block_cache[$row->module][$row->delta] = $row->cache;
    }
  }
  $block = new stdClass();
  $block->cache = $block_cache[$module][$delta];

  // 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[] = $module;
    $cid_parts[] = $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);
  }
}

Functions

Namesort descending Description
blockcache_alter_admin_settings Menu callback, settings page for blockcache alter
blockcache_alter_block Implementation of hook_block().
blockcache_alter_block_view View a cached block using hook_block(). If the block is not cached or the cache is stale, get the info and stick it back into the cache.
blockcache_alter_comment Implementation of hook_comment().
blockcache_alter_flush_caches Implementation of hook_flush_caches().
blockcache_alter_form_alter Implementation of hook_form_alter().
blockcache_alter_menu Implementation of hook_menu().
blockcache_alter_nodeapi Implementation of hook_nodeapi().
blockcache_alter_nodequeue_add Implementation of hook_nodequeue_add().
blockcache_alter_nodequeue_remove Implementation of hook_nodequeue_remove().
blockcache_alter_rehash Blockcache alter rehash function.
blockcache_alter_save_settings Submit callback. Saves block configuration and cache settings per block. This module takes over from block_admin_configure_submit() so we can decide what to clear from cache.
blockcache_alter_user Implementation of hook_user().
_blockcache_alter_check_expire Helper function to check if this block should be refreshed or not.
_blockcache_alter_cleanup Cleanup cached blocks if necessary.
_blockcache_alter_core_patch Helper function to check if a blockcache alter patch is found. Note: it doesn't check which patch is applied.
_blockcache_alter_get_cache_id Get cid for a block.
_blockcache_alter_nodequeue_cleanup Helper function to cleanup cache for nodequeue block.