authcache.helpers.inc in Authenticated User Page Caching (Authcache) 7
Same filename and directory in other branches
Helper functions for the Authcache module (no Drupal hooks here).
File
authcache.helpers.incView source
<?php
/**
* @file
* Helper functions for the Authcache module (no Drupal hooks here).
*/
/**
* Returns an array containing all the roles from account_roles that are not
* present in allowed_roles.
*/
function _authcache_diff_roles($account_roles, $allowed_roles) {
// Remove "authenticated user"-role from the account roles except when it is
// the only role on the account.
if (array_keys($account_roles) != array(
DRUPAL_AUTHENTICATED_RID,
)) {
unset($account_roles[DRUPAL_AUTHENTICATED_RID]);
}
return array_diff_key($account_roles, $allowed_roles);
}
/**
* Should the current page be cached?
*/
function _authcache_is_cacheable() {
global $user, $_authcache_is_ajax, $_authcache_info, $conf;
// Do not cache if...
if (drupal_is_cli()) {
$_authcache_info['no_cache_reason'] = 'Running as CLI script';
return FALSE;
}
if (!empty($_POST)) {
$_authcache_info['no_cache_reason'] = 'POST request';
return FALSE;
}
// disabled this, as it's covered by the "role caching" test
//if ($user->uid == 1) {
// return 'User is Superadmin (user id 1)';
//}
if ($user->uid && !isset($_COOKIE['has_js'])) {
$_authcache_info['no_cache_reason'] = 'Users with JS disabled';
return FALSE;
}
if (isset($_COOKIE['nocache'])) {
$_authcache_info['no_cache_reason'] = 'Caching disabled for session (nocache cookie set)';
return FALSE;
}
if ($_authcache_is_ajax) {
$_authcache_info['no_cache_reason'] = 'Authcache Ajax request';
return FALSE;
}
if (drupal_set_message()) {
$_authcache_info['no_cache_reason'] = 'User notification pending (drupal_set_message)';
return FALSE;
}
if (($ar = explode('?', basename(request_uri()))) && substr(array_shift($ar), -4) == '.php') {
$_authcache_info['no_cache_reason'] = 'PHP files (cron.php, update.php, etc)';
return FALSE;
}
if (empty($conf['cache_backends'])) {
$_authcache_info['no_cache_reason'] = 'no cache_backends defined in settings.php - please see README.txt';
return FALSE;
}
if (variable_get('authcache_noajax', FALSE) && isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
$_authcache_info['no_cache_reason'] = 'Ajax request';
return FALSE;
}
// Check if caching is enabled for user's role
if (!_authcache_is_account_cacheable($user)) {
$_authcache_info['no_cache_reason'] = 'Caching disabled for this users role';
return FALSE;
}
$alias = drupal_get_path_alias($_GET['q']);
$path = drupal_get_normal_path($_GET['q']);
// Normalize path
// Now check page caching settings, defined by the site admin
$pagecaching = variable_get('authcache_pagecaching', array(
array(
'option' => 0,
'pages' => AUTHCACHE_NOCACHE_DEFAULT,
'roles' => array(
DRUPAL_ANONYMOUS_RID,
),
),
));
$i = 0;
$is_cacheable = TRUE;
foreach ($pagecaching as $page_rules) {
// Do caching page roles apply to current user?
$extra_roles = _authcache_diff_roles($user->roles, $page_rules['roles']);
if (empty($extra_roles)) {
switch ($page_rules['option']) {
case '0':
// Cache every page except the listed pages.
case '1':
// Cache only the listed pages.
$page_listed = drupal_match_path($alias, $page_rules['pages']);
if (!!($page_rules['option'] xor $page_listed)) {
$_authcache_info['no_cache_reason'] = 'Caching disabled by Page Ruleset ' . $i;
$is_cacheable = FALSE;
}
break;
case '2':
// Cache pages for which the following PHP code returns TRUE
if (module_exists('php')) {
$result = php_eval($page_rules['pages']);
}
if (empty($result)) {
$_authcache_info['no_cache_reason'] = 'Caching disabled by PHP Rule';
$is_cacheable = FALSE;
}
break;
default:
break;
}
if (!empty($page_rules['noadmin']) && path_is_admin(current_path())) {
$is_cacheable = FALSE;
$_authcache_info['no_cache_reason'] = 'Not caching admin pages';
}
}
$i++;
}
// Backport from 7.x-2.x: Let other modules prevent this request from being
// cached.
if ($is_cacheable) {
$reasons = module_invoke_all('authcache_request_exclude');
if (!empty($reasons)) {
$_authcache_info['no_cache_reason'] = reset($reasons);
$is_cacheable = FALSE;
}
}
return $is_cacheable;
}
/**
* Should user account receive cached pages?
*/
function _authcache_is_account_cacheable($account = FALSE) {
if (!$account) {
global $user;
$account = $user;
}
// Check if caching is enabled for user's role
$cache_roles = variable_get('authcache_roles', array());
// Anonymous
if (!$account->uid && !in_array(DRUPAL_ANONYMOUS_RID, $cache_roles)) {
return FALSE;
}
elseif ($account->uid) {
$extra_roles = _authcache_diff_roles($account->roles, $cache_roles);
if (!empty($extra_roles)) {
return FALSE;
}
}
return TRUE;
}
/**
* 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.
*/
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);
}
}
/**
* Helper function: Invoke hook_$hook() in all modules; merge recursively.
*
* Similar to module_invoke_all(), except $var is passed by reference.
*/
function _authcache_invoke_hook($hook, &$var) {
foreach (module_implements($hook) as $name) {
$function = "{$name}_{$hook}";
$result = $function();
if (isset($result) && is_array($result)) {
$var = array_merge_recursive($var, $result);
}
}
}
/**
* Returns caching key based on user's role.
*
* This is prefixed to the URL in the cache_page bin.
*/
function _authcache_key($account) {
if ($authcache_keygen = variable_get('authcache_key_generator', FALSE)) {
// Call the user specified key generator
return $authcache_keygen($account);
}
else {
// No custom key generator defined so use the default=
if (!$account->uid) {
return '';
}
$keys = implode('.', array_keys($account->roles));
$key = md5($keys . drupal_get_private_key());
// Make sure superuser has its own key not shared with anyone else (though
// it would be better if superuser would'nt be allowed to use the cache in
// the first place).
if ($account->uid == 1) {
$key = md5($key . $keys . drupal_get_private_key());
}
return substr($key, 0, 6);
}
}
/**
* Determines the MIME content type of the current page response based on
* the currently set Content-Type HTTP header.
*
* This should normally return the string 'text/html' unless another module
* has overridden the content type.
*/
function _authcache_get_content_type($default = NULL) {
return drupal_get_http_header("content-type");
}
/**
* Retrun an array of cachable non-html content-types.
*/
function _authcache_get_nonhtml_content_types() {
return preg_split('/(\\r\\n?|\\n)/', AUTHCACHE_NONHTML_CONTENTTYPES, -1, PREG_SPLIT_NO_EMPTY);
}
/**
* Determines the HTTP response code that the current page request will be
* returning by examining the HTTP headers that have been output so far.
*/
function _authcache_get_http_status($status = 200) {
$value = drupal_get_http_header("status");
return isset($value) ? (int) $value : $status;
}
/**
* Returns true if the currently logged in user has access to debug functions.
*/
function _authcache_debug_access() {
global $user;
if (variable_get('authcache_debug_all', FALSE)) {
return TRUE;
}
elseif (!$user->uid) {
return FALSE;
}
else {
return in_array($user->name, variable_get('authcache_debug_users', array()));
}
}
Functions
Name | Description |
---|---|
_authcache_debug_access | Returns true if the currently logged in user has access to debug functions. |
_authcache_diff_roles | Returns an array containing all the roles from account_roles that are not present in allowed_roles. |
_authcache_get_content_type | Determines the MIME content type of the current page response based on the currently set Content-Type HTTP header. |
_authcache_get_http_status | Determines the HTTP response code that the current page request will be returning by examining the HTTP headers that have been output so far. |
_authcache_get_nonhtml_content_types | Retrun an array of cachable non-html content-types. |
_authcache_invoke_hook | Helper function: Invoke hook_$hook() in all modules; merge recursively. |
_authcache_is_account_cacheable | Should user account receive cached pages? |
_authcache_is_cacheable | Should the current page be cached? |
_authcache_key | Returns caching key based on user's role. |
_authcache_shutdown_save_page | Save page to cache |