You are here

function _authcache_shutdown_save_page in Authenticated User Page Caching (Authcache) 7

Same name and namespace in other branches
  1. 6 authcache.helpers.inc \_authcache_shutdown_save_page()

Save page to cache

Called using PHP's register_shutdown_function(). This is better than an ob_start callback since global variables are not deconstructed and the function is executed later.

1 call to _authcache_shutdown_save_page()
authcache_exit in ./authcache.module
Implements hook_exit().

File

./authcache.helpers.inc, line 187
Helper functions for the Authcache module (no Drupal hooks here).

Code

function _authcache_shutdown_save_page() {
  if (drupal_is_cli()) {
    return;
  }
  global $user, $base_root, $_authcache_is_cacheable, $_authcache_info;
  $content_type = _authcache_get_content_type();

  // Find user-specified non-html pages.
  $alias = drupal_get_path_alias($_GET['q']);
  $is_cached_nonhtml = drupal_match_path($alias, variable_get('authcache_nonhtml', AUTHCACHE_NONHTML_DEFAULT)) || in_array($content_type, _authcache_get_nonhtml_content_types()) || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';

  // Last minute checks to cancel saving to page cache.
  if (!$is_cached_nonhtml && strpos($content_type, 'text/html' != 0)) {
    $_authcache_info['no_cache_reason'] = 'Only cache allowed HTTP content types (HTML, JS, etc)';
    $_authcache_is_cacheable = FALSE;
  }
  if (variable_get('authcache_http200', FALSE) && _authcache_get_http_status() != 200) {
    $_authcache_info['no_cache_reason'] = 'Don`t cache 404/403s/etc if specified by admin';
    $_authcache_is_cacheable = FALSE;
  }

  /* simg: headers_sent() seems to always true since php sets a header. removing since I can't see what benefit this achieves?
     if (headers_sent()) {
      $_authcache_info['headers_list'] = implode(' - ',headers_list());
      $_authcache_info['no_cache_reason'] = 'Don`t cache private file transfers or if headers were unexpectly sent.';
      $_authcache_is_cacheable = FALSE ;
    }*/

  // Make sure "Location" redirect isn't used
  foreach (headers_list() as $header) {
    if (strpos($header, "Location:") === 0) {
      $_authcache_info['no_cache_reason'] = 'Location header detected';
      $_authcache_is_cacheable = FALSE;

      //return;
    }
  }

  // Don't cache pages with PHP errors (Drupal can't catch fatal errors)
  if (function_exists('error_get_last') && ($error = error_get_last())) {
    switch ($error['type']) {

      // Ignore these errors:
      case E_NOTICE:

      // run-time notices
      case E_USER_NOTICE:

      // user-generated notice message
      case E_DEPRECATED:

      // run-time notices
      case E_USER_DEPRECATED:

        // user-generated notice message
        break;
      default:

        // Let user know there is PHP error and return
        $_authcache_info['no_cache_reason'] = 'PHP Error:' . error_get_last();
        $_authcache_is_cacheable = FALSE;
        break;
    }
  }

  //Cache key, constructed from user role and URL
  $key = _authcache_key($user) . $base_root . request_uri();

  // Authcache info JSON
  $_authcache_info = array_merge($_authcache_info, array(
    'page_render' => timer_read('page'),
    // Benchmark
    'page_queries' => '-1',
    // Database benchmark, if enabled
    'cache_render' => '-1',
    // Filled by cookie via JS on cache request
    'cache_uid' => $user->uid,
    // Required by JS for HTML updates
    'cache_class' => get_class(_cache_get_object('cache_page')),
    'cache_time' => REQUEST_TIME,
    // Required by JS for HTML updates,
    'is_cacheable' => $_authcache_is_cacheable,
    'cache_key' => $key,
  ));

  // Hide sensitive info from anonymous users
  if (variable_get('authcache_debug_all', FALSE) || !_authcache_debug_access()) {
    unset($_authcache_info['cache_uid']);
    unset($_authcache_info['cache_inc']);
  }

  // Database benchmarks

  //todo:D7 make this work (probably needs moving back into php shutdown function?)
  global $_authcache_queries;
  if (isset($_authcache_queries) && variable_get('dev_query', 0)) {
    $time_query = 0;
    foreach ($_authcache_queries as $q) {
      $time_query += $q[1];
    }
    $time_query = round($time_query * 1000, 2);

    // Convert seconds to milliseconds
    $percent_query = round($time_query / $_authcache_info['page_render'] * 100);
    $_authcache_info['page_queries'] = count($_authcache_queries) . " queries @ {$time_query} ms ({$percent_query}%)";
  }
  else {
    unset($_authcache_info['page_queries']);
  }

  // JSON to send via Ajax
  // The "q" key is need during Ajax phase
  $authcache_ajax = array(
    'q' => $_GET['q'],
  );

  // Invoke hook_authcache_info() operation to allow modules to modify info array
  _authcache_invoke_hook('authcache_info', $_authcache_info);

  // Invoke hook_authcache() operation to allow modules to modify ajax array
  _authcache_invoke_hook('authcache_ajax', $authcache_ajax);

  // Get buffered HTML
  $buffer = ob_get_contents();
  if (substr($buffer, 0, 5) == '<?xml') {
    $is_cached_nonhtml = TRUE;

    // don't append JS to XML pages
  }

  // Add JS for debug mode?
  if ((variable_get('authcache_debug_all', FALSE) || $user->uid && ($debug_users = variable_get('authcache_debug_users', array()))) && !$is_cached_nonhtml) {
    $js = '<script type="text/javascript">
      if (!Authcache) Authcache = {};
      Authcache.info = ' . drupal_json_encode($_authcache_info) . '</script>';

    // Insert JSON before </body>, otherwise just append
    if (strripos($buffer, '</body>') !== FALSE) {
      $buffer = str_replace('</body>', $js . '</body>', $buffer);
    }
    else {
      $buffer .= $js;
    }
    print $js;

    //since by this point the drupal page has already been sent to the browser

    // Also see authcache_authcache_info() for user debug settings
    drupal_add_js($js, array(
      'type' => 'inline',
      'scope' => 'header',
    ));
  }

  // Don't cache empty/dead pages
  if (!$buffer) {
    return;
  }
  $path = drupal_get_normal_path($_GET['q']);

  // normalize path
  // Only place JSON info for HTML pages
  if (!$is_cached_nonhtml && $user->uid) {
    $authcache_footer['info'] = $_authcache_info;
    $authcache_footer['ajax'] = $authcache_ajax;
    $authcache_json = "\n<!-- Authcache Footer JSON -->\n" . "<script type=\"text/javascript\">\nvar authcacheFooter = " . drupal_json_encode($authcache_footer) . ";\n</script>\n";

    // Insert JSON before </body>, otherwise just append
    if (strripos($buffer, '</body>') !== FALSE) {
      $buffer = str_replace('</body>', $authcache_json . '</body>', $buffer);
    }
    else {
      $buffer .= $authcache_json;
    }
  }

  //header("Content-Length: " . strlen($output)); //D7 seems to have sent headers earlier, so doesn't seem possible to set headers at this point
  flush();

  // Final check, in case variable was modified
  if ($_authcache_is_cacheable !== true) {
    return;
  }

  // Check whether the current page might be compressed.
  $page_compressed = variable_get('page_compression', TRUE) && extension_loaded('zlib');
  $cache = (object) array(
    'cid' => $key,
    'data' => array(
      'path' => $_GET['q'],
      'body' => $buffer,
      'title' => drupal_get_title(),
      'headers' => array(),
      // We need to store whether page was compressed or not,
      // because by the time it is read, the configuration might change.
      'page_compressed' => $page_compressed,
    ),
    'expire' => CACHE_TEMPORARY,
    'created' => REQUEST_TIME,
  );

  // Restore preferred header names based on the lower-case names returned by drupal_get_http_header().
  $header_names = _drupal_set_preferred_header_name();
  foreach (drupal_get_http_header() as $name_lower => $value) {
    $cache->data['headers'][$header_names[$name_lower]] = $value;
    if ($name_lower == 'expires') {

      // Use the actual timestamp from an Expires header if available.
      $cache->expire = strtotime($value);
    }
  }
  if ($cache->data['body']) {
    if ($page_compressed) {
      $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
    }
    cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
  }
}