You are here

views_content_cache.module in Views content cache 6.2

Same filename and directory in other branches
  1. 7.3 views_content_cache.module

Views content cache cache.

File

views_content_cache.module
View source
<?php

/**
 * @file
 *   Views content cache cache.
 */

/**
 * Implementation of hook_views_api().
 */
function views_content_cache_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'views_content_cache') . '/views',
  );
}

/**
 * Implementation of hook_nodeapi().
 */
function views_content_cache_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
    case 'delete revision':
      views_content_cache_update_set($node, 'node');
      break;
  }
}

/**
 * Implementation of hook_comment().
 */
function views_content_cache_comment(&$a1, $op) {
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
    case 'publish':
    case 'unpublish':
      if (is_object($a1)) {
        $arr = (array) drupal_clone($a1);
      }
      else {
        $arr = $a1;
      }
      if (!empty($arr['nid']) && ($node = node_load($arr['nid']))) {
        views_content_cache_update_set($arr, 'comment');
      }
      break;
  }
}

/**
 * Implementation of hook_votingapi_results().
 */
function views_content_cache_votingapi_results($cached, $content_type, $content_id) {
  views_content_cache_update_set($cached, 'votingapi_results');
}

/**
 * Implementation of hook_nodequeue_add().
 */
function views_content_cache_nodequeue_add($sqid, $nid) {
  $subqueue = nodequeue_load_subqueue($sqid);
  views_content_cache_update_set($subqueue, 'nodequeue');
}

/**
 * Implementation of hook_nodequeue_remove().
 */
function views_content_cache_nodequeue_remove($sqid, $nid) {
  $subqueue = nodequeue_load_subqueue($sqid);
  views_content_cache_update_set($subqueue, 'nodequeue');
}

/**
 * Implementation of hook_nodequeue_sort_alter().
 */
function views_content_cache_nodequeue_sort_alter($nodes, $sqid) {

  /**
   * In Nodequeue 6.2.11 this changed from a module hook invoked
   * via module_invoke_all() to a drupal alter hook invoked
   * via drupal_alter(). In this change the parameter order was
   * swapped so the logic below attempts to handle both the old
   * and new versions of Nodequeue.
   */

  // Pre 6.2.11
  if (!is_array($nodes) && is_numeric($nodes)) {
    $tmp = $sqid;
    $sqid = $nodes;
    $nodes = $tmp;
  }
  $subqueue = nodequeue_load_subqueue($sqid);
  views_content_cache_update_set($subqueue, 'nodequeue');
}

/**
 * Create one or more update records for the given object.
 *
 * @param object $object
 *   The object for which the update is occurring. Example: a $node object.
 * @param string $object_type
 *   A string identifier that other modules can use to identify the type of
 *   object passed to them. Example: 'node'.
 * @param int $timestamp
 *   A timestamp to use for the update record. Optional.
 *
 * @return boolean
 *   Returns TRUE if one or more update records were written. FALSE if none
 *   were written.
 */
function views_content_cache_update_set($object, $object_type, $timestamp = NULL) {
  $update = FALSE;
  $timestamp = isset($timestamp) ? $timestamp : time();
  if ($cids = views_content_cache_id_generate($object, $object_type)) {
    foreach ($cids as $cid) {
      if (!empty($cid)) {
        $update = TRUE;
        $mapped = views_content_cache_id_record($cid);

        // Remove any update records that match this cid.
        $where = array();
        $args = array();
        foreach ($mapped as $key_id => $key_value) {
          if (isset($key_value)) {
            $where[] = "{$key_id} = " . db_placeholders($key_value, 'varchar');
            $args[] = $key_value;
          }
          else {
            $where[] = "{$key_id} IS NULL";
          }
        }
        if (!empty($where)) {
          $where = implode(' AND ', $where);
          db_query("DELETE FROM {views_content_cache} WHERE {$where}", $args);
        }
        $mapped['timestamp'] = $timestamp;
        drupal_write_record('views_content_cache', $mapped);
      }
    }
  }
  return $update;
}

/**
 * Retrieve the latest update record for a given cache id.
 *
 * @param array $cid
 *   A cache id array. May contain multiple values for each key id.
 * @param boolean $reset
 *   Reset the internal static cache of timestamps for particular keys.
 *
 * @return int
 *   Returns the timestamp of the latest update record matching the given cache
 *   id or FALSE if none was found.
 */
function views_content_cache_update_get($cid, $reset = FALSE) {
  static $cache = array();
  $hash = md5(serialize($cid));
  if (!isset($cache[$hash]) || $reset) {
    $mapped = views_content_cache_id_record($cid);

    // Generate the where clause from the cache id.
    $where_query = array(
      '#glue' => 'OR',
      array(
        '#glue' => 'AND',
      ),
    );
    foreach ($mapped as $key_id => $key_value) {
      if (!empty($key_value)) {

        // Use a simpler syntax for single value wheres:
        if (count($key_value) == 1) {
          $where = "{$key_id} = " . db_placeholders($key_value, 'varchar');
        }
        else {
          $where = "{$key_id} IN (" . db_placeholders($key_value, 'varchar') . ")";
        }

        // Work out where the clause should be put in the query array:
        if ('AND' == views_content_cache_and_or_key_get($key_id)) {

          // AND queries go to a sub array of the clause:
          $where_query[0][] = array(
            '#clause' => $where,
            '#args' => array_values($key_value),
          );
        }
        else {

          // OR queries go to the base clause:
          $where_query[] = array(
            '#clause' => $where,
            '#args' => array_values($key_value),
          );
        }
      }
    }
    $built_clause = views_content_cache_query_builder($where_query);

    // If there were arguments in the query, run it.
    if (!empty($built_clause['#args'])) {
      $cache[$hash] = db_result(db_query("SELECT timestamp FROM {views_content_cache} WHERE {$built_clause['#clause']} ORDER BY timestamp DESC", $built_clause['#args']));
    }
    else {
      $cache[$hash] = FALSE;
    }
  }
  return $cache[$hash];
}

/**
 * Determine how this cache segment should be combined with others.
 *
 * @param
 *   The cache segment, should be c1 or c2, etc.
 * @return
 *   Either 'AND' or 'OR' depending on how this cache segment can be combined
 *   with the others.
 */
function views_content_cache_and_or_key_get($cid) {
  $map = views_content_cache_id_schema();
  if ($key = array_search($cid, $map)) {
    $plugin = views_content_cache_get_plugin($key);
    return $plugin
      ->clause_mode();
  }
  return 'AND';
}

/**
 * Generate one or more cache ids for an update record.
 *
 * @param object $object
 *   The object for which the update is occurring. Example: a $node object.
 * @param string $object_type
 *   A string identifier that other modules can use to identify the type of
 *   object passed to them. Example: 'node'.
 *
 * @return array
 *   Returns an array of cache ids.
 */
function views_content_cache_id_generate($object, $object_type) {
  $cids = array(
    array(),
  );
  foreach (views_content_cache_get_plugin() as $key_id => $plugin) {
    $key_values = $plugin
      ->content_key($object, $object_type);
    if (!empty($key_values)) {

      // Ensure that we have an array to work with.
      $key_values = is_array($key_values) ? $key_values : array(
        $key_values,
      );

      // If the content key is multivalue create an additional cid per value.
      $processed = array();
      foreach ($key_values as $key_value) {
        foreach ($cids as $cid) {
          $cid[$key_id] = $key_value;
          $processed[] = $cid;
        }
      }
      $cids = $processed;
    }
  }
  return $cids;
}

/**
 * Convert a cache id to an update record suitable for drupal_write_record() or
 * use in a SELECT query.
 *
 * @param array $cid
 *   An array representing a cache id where keys correspond to plugin key IDs
 *   and values are the cache id values generated by each plugin.
 *
 * @return array
 *   An array where each plugin key ID has been replaced by one of the
 *   corresponding database columns c1 through c8.
 */
function views_content_cache_id_record($cid) {
  $map = views_content_cache_id_schema();
  $record = array();
  foreach ($cid as $key_id => $key_value) {
    if ($key_id === 'timestamp') {
      $record[$key_id] = $key_value;
    }
    else {
      if (isset($map[$key_id]) && ($column = $map[$key_id])) {
        $record[$column] = $key_value;
      }
    }
  }
  return $record;
}

/**
 * Retrieve or generate the cache id to schema mapping.
 *
 * We store the schema mapping in the main cache table/bin, this means that it
 * can get invalidated quite quickly, but this will also probably coincide with
 * the views cache being flushed, so we're are just wasting a few CPU cycles in
 * reality.
 *
 * @param boolean $reset
 *   Reset the static and DB cache for the schema mapping.
 *
 * @return array
 *   An array where each key is a plugin key ID and each value is the
 *   corresponding database column.
 */
function views_content_cache_id_schema($reset = FALSE) {
  static $map;
  if (!isset($map) || $reset) {
    $cache = cache_get('views_content_cache_id_schema');
    if (!$reset && !empty($cache->data)) {
      $map = $cache->data;
    }
    else {
      $cache_keys = array_keys(views_content_cache_get_plugin());
      $i = 1;
      foreach ($cache_keys as $key_id) {

        // Schema is limited to 8.
        if ($i > 8) {
          break;
        }
        $map[$key_id] = "c{$i}";
        $i++;
      }

      // If the newly generated map and the prior map do not match invalidate
      // all cache update records.
      if (empty($cache->data) || $map != $cache->data) {
        db_query("TRUNCATE {views_content_cache}");

        // This is probably too aggressive. @TODO: See if we can surgically
        // invalidate only views that use VCC.
        views_invalidate_cache();
      }
      cache_set('views_content_cache_id_schema', $map, 'cache');
    }
  }
  return $map;
}

/**
 * Retrieve a plugin object.
 *
 * @param string $plugin
 *   The name of the plugin class to retrieve. Optional.
 * @param boolean $reset
 *   Reset the static cache.
 *
 * @return mixed
 *   If a specific plugin class was requested an instance of that class is
 *   returned. Otherwise, an array of all plugins.
 */
function views_content_cache_get_plugin($plugin = NULL, $reset = FALSE) {
  static $cache;
  if (!isset($cache) || $reset) {
    ctools_include('plugins');
    $plugins = ctools_get_plugins('views_content_cache', 'plugins');

    // This ksort is critical. This ensures that our plugins are in consistent
    // order, especially important for views_content_cache_id_schema().
    ksort($plugins);
    foreach ($plugins as $key => $info) {
      if (empty($info['abstract']) && ($class = ctools_plugin_get_class($info, 'handler'))) {
        $cache[$key] = new $class();
      }
    }
  }
  if (isset($plugin)) {
    return isset($cache[$plugin]) ? $cache[$plugin] : FALSE;
  }
  return $cache;
}

/**
 * A simple SQL where clause builder.
 *
 * This function will recursively build a WHERE clause.
 *
 * @return
 *   An array with two keyed, values:
 *     - 'clause' - containing the meat of a WHERE clause.
 *     - 'args' - An array of arguments for the db_query call.
 */
function views_content_cache_query_builder($query_array) {

  // Arrays of clauses and args:
  $clauses = array();
  $args = array();

  // First flatten this array:
  foreach (element_children($query_array) as $key) {
    if (is_array($query_array[$key]) && isset($query_array[$key]['#glue'])) {
      $query_array[$key] = views_content_cache_query_builder($query_array[$key]);
    }
    if (isset($query_array[$key]['#clause'])) {
      $clauses[] = $query_array[$key]['#clause'];
      if (is_array($query_array[$key]['#args'])) {
        $args = array_merge($args, $query_array[$key]['#args']);
      }
    }
  }
  $glue = isset($query_array['#glue']) ? $query_array['#glue'] : 'AND';

  // If we are ORing, we need to add some brackets
  return array(
    '#clause' => '(' . implode(" {$glue} ", $clauses) . ')',
    '#args' => $args,
  );
}

/**
 * CTools plugins API hooks ===================================================
 */

/**
 * Implementation of hook_ctools_plugin_api().
 */
function views_content_cache_ctools_plugin_api($module, $api) {
  if ($module == 'views_content_cache' && $api == 'plugins') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * Implementation of hook_ctools_plugin_plugins().
 */
function views_content_cache_ctools_plugin_plugins() {
  return array(
    'cache' => TRUE,
    'use hooks' => TRUE,
  );
}

/**
 * Implementation of hook_context_plugins().
 *
 * This is a ctools plugins hook.
 */
function views_content_cache_views_content_cache_plugins() {
  $plugins = array();
  $plugins['base'] = array(
    'abstract' => TRUE,
    'handler' => array(
      'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
      'file' => 'base.inc',
      'class' => 'views_content_cache_key',
    ),
  );
  $plugins['node'] = array(
    'title' => t('Node type'),
    'description' => t('Invalidates cache by node type'),
    'handler' => array(
      'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
      'file' => 'node.inc',
      'class' => 'views_content_cache_key_node',
      'parent' => 'base',
    ),
  );
  $plugins['node_only'] = array(
    'title' => t('Pure node updates only'),
    'description' => t('Invalidates cache for main node operations'),
    'handler' => array(
      'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
      'file' => 'node_only.inc',
      'class' => 'views_content_cache_key_node_only',
      'parent' => 'base',
    ),
  );
  if (module_exists('comment')) {
    $plugins['comment'] = array(
      'title' => t('Comment'),
      'description' => t('Invalidates cache when comments are posted or updated'),
      'handler' => array(
        'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
        'file' => 'comment.inc',
        'class' => 'views_content_cache_key_comment',
        'parent' => 'base',
      ),
    );
  }
  if (module_exists('og')) {
    $plugins['og'] = array(
      'handler' => array(
        'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
        'file' => 'og.inc',
        'class' => 'views_content_cache_key_og',
        'parent' => 'base',
      ),
    );
  }
  if (module_exists('nodequeue')) {
    $plugins['nodequeue'] = array(
      'title' => t('Nodequeue'),
      'description' => t('Invalidates cache when a nodequeue is altered'),
      'handler' => array(
        'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
        'file' => 'nodequeue.inc',
        'class' => 'views_content_cache_key_nodequeue',
        'parent' => 'base',
      ),
    );
  }
  if (module_exists('votingapi')) {
    $plugins['votingapi'] = array(
      'handler' => array(
        'path' => drupal_get_path('module', 'views_content_cache') . '/plugins',
        'file' => 'votingapi.inc',
        'class' => 'views_content_cache_key_votingapi',
        'parent' => 'base',
      ),
    );
  }
  return $plugins;
}

Functions

Namesort descending Description
views_content_cache_and_or_key_get Determine how this cache segment should be combined with others.
views_content_cache_comment Implementation of hook_comment().
views_content_cache_ctools_plugin_api Implementation of hook_ctools_plugin_api().
views_content_cache_ctools_plugin_plugins Implementation of hook_ctools_plugin_plugins().
views_content_cache_get_plugin Retrieve a plugin object.
views_content_cache_id_generate Generate one or more cache ids for an update record.
views_content_cache_id_record Convert a cache id to an update record suitable for drupal_write_record() or use in a SELECT query.
views_content_cache_id_schema Retrieve or generate the cache id to schema mapping.
views_content_cache_nodeapi Implementation of hook_nodeapi().
views_content_cache_nodequeue_add Implementation of hook_nodequeue_add().
views_content_cache_nodequeue_remove Implementation of hook_nodequeue_remove().
views_content_cache_nodequeue_sort_alter Implementation of hook_nodequeue_sort_alter().
views_content_cache_query_builder A simple SQL where clause builder.
views_content_cache_update_get Retrieve the latest update record for a given cache id.
views_content_cache_update_set Create one or more update records for the given object.
views_content_cache_views_api Implementation of hook_views_api().
views_content_cache_views_content_cache_plugins Implementation of hook_context_plugins().
views_content_cache_votingapi_results Implementation of hook_votingapi_results().