view = &$view; $this->display = &$display; if (is_object($display->handler)) { $options = $display->handler->get_option('cache'); // Overlay incoming options on top of defaults. $this->unpack_options($this->options, $options); } } /** * Return a string to display as the clickable title for the * access control. */ public function summary_title() { return t('Unknown'); } /** * Determine the expiration time of the cache type, or NULL if no expire. * * Plugins must override this to implement expiration. * * @param string $type * The cache type, either 'query', 'result' or 'output'. */ public function cache_expire($type) { } /** * Determine expiration time in the cache table of the cache type. * * Or CACHE_PERMANENT if item shouldn't be removed automatically from cache. * * Plugins must override this to implement expiration in the cache table. * * @param string $type * The cache type, either 'query', 'result' or 'output'. */ public function cache_set_expire($type) { return CACHE_PERMANENT; } /** * Save data to the cache. * * A plugin should override this to provide specialized caching behavior. */ public function cache_set($type) { switch ($type) { case 'query': // Not supported currently, but this is certainly where we'd put it. break; case 'results': $data = array( 'result' => $this->view->result, 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0, 'current_page' => $this->view->get_current_page(), ); cache_set($this->get_results_key(), $data, $this->table, $this->cache_set_expire($type)); break; case 'output': $this->gather_headers(); $this->storage['output'] = $this->view->display_handler->output; cache_set($this->get_output_key(), $this->storage, $this->table, $this->cache_set_expire($type)); break; } } /** * Retrieve data from the cache. * * A plugin should override this to provide specialized caching behavior. */ public function cache_get($type) { $cutoff = $this->cache_expire($type); switch ($type) { case 'query': // Not supported currently, but this is certainly where we'd put it. return FALSE; case 'results': // Values to set: $view->result, $view->total_rows, $view->execute_time, // $view->current_page. if ($cache = cache_get($this->get_results_key(), $this->table)) { if (!$cutoff || $cache->created > $cutoff) { $this->view->result = $cache->data['result']; $this->view->total_rows = $cache->data['total_rows']; $this->view->set_current_page($cache->data['current_page']); $this->view->execute_time = 0; return TRUE; } } return FALSE; case 'output': if ($cache = cache_get($this->get_output_key(), $this->table)) { if (!$cutoff || $cache->created > $cutoff) { $this->storage = $cache->data; $this->view->display_handler->output = $cache->data['output']; $this->restore_headers(); return TRUE; } } return FALSE; } } /** * Clear out cached data for a view. * * We're just going to nuke anything related to the view, regardless of * display, to be sure that we catch everything. Maybe that's a bad idea. */ public function cache_flush() { cache_clear_all($this->view->name . ':', $this->table, TRUE); } /** * Post process any rendered data. * * This can be valuable to be able to cache a view and still have some level * of dynamic output. In an ideal world, the actual output will include HTML * comment based tokens, and then the post process can replace those tokens. * * Example usage. If it is known that the view is a node view and that the * primary field will be a nid, you can do something like this: * * * * And then in the post render, create an array with the text that should * go there: * * strtr($output, array('', 'output for FIELD of nid 1'); * * All of the cached result data will be available in $view->result, as well, * so all ids used in the query should be discoverable. */ public function post_render(&$output) { } /** * Start caching JavaScript, css and other out of band info. * * This takes a snapshot of the current system state so that we don't * duplicate it. Later on, when gather_headers() is run, this information * will be removed so that we don't hold onto it. */ public function cache_start() { $this->storage['head'] = drupal_add_html_head(); $this->storage['css'] = drupal_add_css(); $this->storage['js'] = drupal_add_js(); $this->storage['headers'] = drupal_get_http_header(); } /** * Gather out of band data, compare it to the start data and store the diff. */ public function gather_headers() { // Simple replacement for head. if (isset($this->storage['head'])) { $this->storage['head'] = str_replace($this->storage['head'], '', drupal_add_html_head()); } else { $this->storage['head'] = ''; } // Check if the advanced mapping function of D 7.23 is available. $array_mapping_func = function_exists('drupal_array_diff_assoc_recursive') ? 'drupal_array_diff_assoc_recursive' : 'array_diff_assoc'; // Slightly less simple for CSS. $css = drupal_add_css(); $css_start = isset($this->storage['css']) ? $this->storage['css'] : array(); $this->storage['css'] = $this->assetDiff($css, $css_start, $array_mapping_func); // Get JavaScript after/before views renders. $js = drupal_add_js(); $js_start = isset($this->storage['js']) ? $this->storage['js'] : array(); // If there are any differences between the old and the new JavaScript then // store them to be added later. $this->storage['js'] = $this->assetDiff($js, $js_start, $array_mapping_func); // Special case the settings key and get the difference of the data. $settings = isset($js['settings']['data']) ? $js['settings']['data'] : array(); $settings_start = isset($js_start['settings']['data']) ? $js_start['settings']['data'] : array(); $this->storage['js']['settings'] = $array_mapping_func($settings, $settings_start); // Get difference of HTTP headers. $this->storage['headers'] = $array_mapping_func(drupal_get_http_header(), $this->storage['headers']); } /** * Computes the differences between two JS/CSS asset arrays. * * @param array $assets * The current asset array. * @param array $start_assets * The original asset array. * @param string $diff_function * The function that should be used for computing the diff. * * @return array * A CSS or JS asset array that contains all entries that are new/different * in $assets. */ protected function assetDiff(array $assets, array $start_assets, $diff_function) { $diff = $diff_function($assets, $start_assets); // Cleanup the resulting array since drupal_array_diff_assoc_recursive() can // leave half populated arrays behind. foreach ($diff as $key => $entry) { // If only the weight was different we can remove this entry. if (count($entry) == 1 && isset($entry['weight'])) { unset($diff[$key]); } // If there are other differences we override with the latest entry. elseif ($entry != $assets[$key]) { $diff[$key] = $assets[$key]; } } return $diff; } /** * Restore out of band data saved to cache. Copied from Panels. */ public function restore_headers() { if (!empty($this->storage['head'])) { drupal_add_html_head($this->storage['head']); } if (!empty($this->storage['css'])) { foreach ($this->storage['css'] as $args) { drupal_add_css($args['data'], $args); } } if (!empty($this->storage['js'])) { foreach ($this->storage['js'] as $key => $args) { if ($key !== 'settings') { drupal_add_js($args['data'], $args); } else { foreach ($args as $setting) { drupal_add_js($setting, 'setting'); } } } } if (!empty($this->storage['headers'])) { foreach ($this->storage['headers'] as $name => $value) { drupal_add_http_header($name, $value); } } } /** * */ public function get_results_key() { if (!isset($this->_results_key)) { $key_data = array(); foreach (array('exposed_info', 'page', 'sort', 'order', 'items_per_page', 'offset') as $key) { if (isset($_GET[$key])) { $key_data[$key] = $_GET[$key]; } } $this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key($key_data); } return $this->_results_key; } /** * */ public function get_output_key() { if (!isset($this->_output_key)) { $key_data = array( 'result' => $this->view->result, 'theme' => $GLOBALS['theme'], ); $this->_output_key = $this->view->name . ':' . $this->display->id . ':output:' . $this->get_cache_key($key_data); } return $this->_output_key; } /** * Returns cache key. * * @param array $key_data * Additional data for cache segmentation and/or overrides for default * segmentation. * * @return string */ public function get_cache_key($key_data = array()) { global $user; $key_data += array( 'roles' => array_keys($user->roles), 'super-user' => $user->uid == 1, // special caching for super user. 'language' => $GLOBALS['language']->language, 'language_content' => $GLOBALS['language_content']->language, 'base_url' => $GLOBALS['base_url'], ); if (empty($key_data['build_info'])) { $build_info = $this->view->build_info; foreach (array('query', 'count_query') as $index) { // If the default query back-end is used generate SQL query strings from // the query objects. if ($build_info[$index] instanceof SelectQueryInterface) { $query = $build_info[$index]; // If the query was not yet prepared, work on a clone and run // preExecute(). if (!$query->isPrepared()) { $query = clone $build_info[$index]; $query->preExecute(); } $key_data['build_info'][$index] = array( 'sql' => (string) $query, 'arguments' => $query->getArguments(), ); } } } return md5(serialize($key_data)); } } /** * @} */