Extract role-check into utility function authcache_diff_roles
[project/authcache.git] / authcache.module
1 <?php
2
3 /**
4 * @file
5 * Authenticated User Page Caching (and anonymous users, too!)
6 *
7 * @see authcache.admin.inc for admin page functionality
8 */
9
10 // Default caching rules (Never cache these pages)
11 define('AUTHCACHE_NOCACHE_DEFAULT', '
12 user*
13 node/add/*
14 node/*/edit
15 node/*/track
16 tracker*
17 comment/edit*
18 civicrm*
19 cart*
20 system/files/*
21 system/temporary*
22 ');
23
24 // Default non-HTML cachable content-types
25 define('AUTHCACHE_MIMETYPE_DEFAULT', '
26 text/html
27 text/javascript
28 text/plain
29 application/xml
30 application/atom+xml
31 ');
32
33 // Flags for authcache_fix_cookies:
34 define('AUTHCACHE_FLAGS_NONE', 0x0);
35 define('AUTHCACHE_FLAGS_ACCOUNT_ENABLED', 0x1);
36 define('AUTHCACHE_FLAGS_LOGIN_ACTION', 0x2);
37 define('AUTHCACHE_FLAGS_LOGOUT_ACTION', 0x4);
38
39 /**
40 * Implements hook_menu().
41 */
42 function authcache_menu() {
43
44 $items['admin/config/system/authcache'] = array(
45 'title' => 'Authcache',
46 'description' => 'Configure authenticated user page caching.',
47 'page callback' => 'drupal_get_form',
48 'page arguments' => array('authcache_admin_config'),
49 'access arguments' => array('administer site configuration'),
50 'file' => 'authcache.admin.inc',
51 'weight' => 10,
52 );
53
54 $items['admin/config/system/authcache/config'] = array(
55 'title' => 'Configuration',
56 'type' => MENU_DEFAULT_LOCAL_TASK,
57 'weight' => -10,
58 );
59
60 $items['admin/config/system/authcache/pagecaching'] = array(
61 'title' => 'Page caching settings',
62 'description' => "Configure page cache settings.",
63 'page callback' => 'drupal_get_form',
64 'page arguments' => array('authcache_admin_pagecaching'),
65 'access arguments' => array('administer site configuration'),
66 'file' => 'authcache.admin.inc',
67 'type' => MENU_LOCAL_TASK,
68 'weight' => 20,
69 );
70 $items['authcache/ajax'] = array(
71 'title' => 'Javascript ajax Callback',
72 'page callback' => 'authcache_ajax',
73 'access arguments' => array('administer site configuration'),
74 'file' => 'authcache.admin.inc',
75 'type' => MENU_CALLBACK,
76 );
77
78
79 return $items;
80 }
81
82 /**
83 * Implements hook_module_implements_alter().
84 *
85 * Make sure that hook_init of this module is called before all other modules
86 * and vice versa for hook_exit.
87 */
88 function authcache_module_implements_alter(&$implementations, $hook) {
89 if ($hook == 'init') {
90 $me = $implementations['authcache'];
91 unset($implementations['authcache']);
92 $implementations = array_merge(array('authcache' => $me), $implementations);
93 }
94
95 if ($hook == 'exit') {
96 $me = $implementations['authcache'];
97 unset($implementations['authcache']);
98 $implementations['authcache'] = $me;
99 }
100 }
101
102 /**
103 * Implements hook_init().
104 */
105
106 function authcache_init() {
107 global $conf, $user;
108
109 $reasons = module_invoke_all('authcache_request_exclude');
110 if (!empty($reasons)) {
111 _authcache_exclude(reset($reasons));
112 }
113
114 $reasons = module_invoke_all('authcache_account_exclude', $user);
115 if (!empty($reasons)) {
116 _authcache_exclude(reset($reasons));
117 }
118
119 if (!authcache_excluded()) {
120 // Don't allow format_date() to use the user's local timezone
121 $conf['configurable_timezones'] = FALSE;
122
123 // Start output buffering
124 ob_start();
125 }
126
127 // Attach required JavaScript
128 drupal_add_library('system', 'jquery.cookie');
129 drupal_add_js(drupal_get_path('module', 'authcache') . '/authcache.js');
130
131 // Inject authcache cookie settings.
132 $lifetime = ini_get('session.cookie_lifetime');
133 $lifetime = (!empty($lifetime) && is_numeric($lifetime) ? (int)$lifetime : 0);
134 drupal_add_js(array('authcache' => array(
135 'q' => $_GET['q'],
136 'cp' => array(
137 'path' => ini_get('session.cookie_path'),
138 'domain' => ini_get('session.cookie_domain'),
139 'secure' => ini_get('session.cookie_secure') == '1',
140 ),
141 'cl' => $lifetime/86400,
142 )), 'setting');
143
144 // Fix cookies if necessary.
145 $flags = (authcache_account_allows_caching()) ?
146 AUTHCACHE_FLAGS_ACCOUNT_ENABLED : AUTHCACHE_FLAGS_NONE;
147 authcache_fix_cookies($flags);
148 }
149
150
151 /**
152 * Implements hook_user_login().
153 */
154 function authcache_user_login(&$edit, $account) {
155 $flags = AUTHCACHE_FLAGS_LOGIN_ACTION;
156
157 if (authcache_account_allows_caching($account)) {
158 $flags |= AUTHCACHE_FLAGS_ACCOUNT_ENABLED;
159 }
160
161 authcache_fix_cookies($flags, $account);
162 }
163
164
165 /**
166 * Implements hook_user_logout().
167 */
168 function authcache_user_logout($account) {
169 // Note: include same cookie deletion in ajax/authcache.module
170 authcache_fix_cookies(AUTHCACHE_FLAGS_LOGOUT_ACTION, $account);
171 }
172
173
174 /**
175 * Implements hook_form_alter(),
176 */
177 function authcache_form_alter(&$form, &$form_state, $form_id) {
178 if (authcache_page_is_cacheable()) {
179 // Need to postpone the decision whether the form and the page is cacheable
180 // to an after-build callback.
181 $form['#after_build'][] = '_authcache_form_after_build';
182 }
183
184 // Alter all forms
185 switch ($form_id) {
186 // Alter Drupal's "Performance" admin form
187 case 'system_performance_settings':
188 $form['caching']['cache']['#description'] = ' <strong>' . t('If Authcache is enabled for the "anonymous user" role, Drupal\'s built-in page caching will be automatically disabled since all page caching is done through Authcache API instead of Drupal core.') . '</strong>';
189 if (authcache_account_allows_caching(drupal_anonymous_user())) {
190 $form['caching']['cache']['#disabled'] = TRUE; //array(0 => t('Disabled') . ' ' . t('by') . ' Authcache');
191 $form['caching']['cache']['#value'] = TRUE;
192 }
193
194 break;
195 case 'user_profile_form':
196 // Don't allow user local timezone
197 if (authcache_account_allows_caching()) {
198 unset($form['timezone']);
199 }
200 break;
201 }
202 }
203
204 /**
205 * Form after_build callback for all forms on cacheable pages
206 *
207 * Disable storing the form to form-cache if possible. However some forms (especially
208 * Ajax-enabled ones) require the form cache. In this case page-caching must be
209 * cancelled.
210 *
211 * @see drupal_build_form().
212 */
213 function _authcache_form_after_build($form, $form_state) {
214 $form_id = $form['#form_id'];
215
216 if (isset($form['form_token']) && !authcache_get_request_property('ajax')) {
217 authcache_cancel(t('Form with CSRF protected on page but cannot use AJAX to defer token-retrieval.'));
218 }
219 else {
220 if (empty($form_state['rebuild']) && empty($form_state['cache'])) {
221 // Disable form cache and remove build_id if caching is not explicitely requested
222 $form_state['no_cache'] = TRUE;
223 unset($form['form_build_id']);
224 unset($form['#build_id']);
225 }
226
227 if (isset($form['form_token']) && authcache_get_request_property('ajax')) {
228 // Remove CSRF-token from built form and make sure it can be retrieved
229 // later using AJAX.
230 unset($form['form_token']);
231
232 drupal_add_js(drupal_get_path('module', 'authcache') . '/authcache.formtokenids.js');
233 drupal_add_js(array('aceformtokenids' => array(
234 $form_id => (isset($form['#token'])) ? $form['#token'] : $form_id,
235 )), 'setting');
236 }
237 }
238
239 return $form;
240 }
241
242 /**
243 * Process page template variables.
244 */
245 function authcache_preprocess_page(&$variables) {
246 if (user_is_logged_in() && authcache_page_is_cacheable()) {
247 if (authcache_get_request_property('ajax')) {
248 drupal_add_js(drupal_get_path('module', 'authcache') . '/authcache.tabs.js');
249 }
250 $variables['tabs']['#post_render'][] = 'authcache_wrap_tabs';
251 $variables['action_links']['#post_render'][] = 'authcache_wrap_local_actions';
252 }
253 }
254
255 /**
256 * Post-render callback for page-tabs. Wrap them into an authcache span, so we
257 * can find it again in JavaScript.
258 */
259 function authcache_wrap_tabs($markup) {
260 if (!empty($markup)) {
261 if (authcache_get_request_property('ajax')) {
262 $markup = '<span id="authcache-tabs">' . $markup . '</span>';
263 }
264 else {
265 authcache_cancel(t('Tabs on page but Authcache AJAX not enabled.'));
266 }
267 }
268
269 return $markup;
270 }
271
272 /**
273 * Post-render callback for local actions. Wrap them into an authcache span, so
274 * we can find it again in JavaScript.
275 */
276 function authcache_wrap_local_actions($markup) {
277 if (!empty($markup)) {
278 if (authcache_get_request_property('ajax')) {
279 $markup = '<span id="authcache-local-actions">' . $markup . '</span>';
280 }
281 else {
282 authcache_cancel(t('Local actions on page but Authcache AJAX not enabled.'));
283 }
284 }
285
286 return $markup;
287 }
288
289 /**
290 * Implements hook_exit().
291 *
292 * Called on drupal_goto() redirect.
293 * Make sure status messages show up, if applicable.
294 */
295 function authcache_exit($destination = NULL) {
296 // Cancel caching when hook_exit was called from drupal_goto.
297 if ($destination !== NULL) {
298 authcache_cancel(t('Redirecting to @destination', array('@destination' => $destination)));
299 }
300
301 // Disable authcache on next page request if there are messages pending which
302 // did not manage it onto the current page.
303 if (drupal_set_message()) {
304 authcache_fix_cookies(AUTHCACHE_FLAGS_NONE);
305 }
306
307 // If this page was excluded in hook_init, we're done here.
308 if (authcache_excluded()) {
309 return;
310 }
311
312 // Forcibly disable drupal built-in page caching for anonymous users.
313 // Prevent drupal_page_set_cache() called from drupal_page_footer() to
314 // store the page a second time after we did.
315 drupal_page_is_cacheable(FALSE);
316
317 // Cache and output
318 if ($cache = authcache_page_set_cache()) {
319 drupal_serve_page_from_cache($cache);
320 }
321 else {
322 ob_end_flush();
323 }
324 }
325
326 //
327 // Preprocess functions
328 //
329
330 /**
331 * Implements hook_preprocess().
332 *
333 * Inject authcache variables into every template.
334 */
335 function authcache_preprocess(&$variables, $hook) {
336 // Define variables for templates files
337 $variables['authcache_is_cacheable'] = authcache_page_is_cacheable();
338 }
339
340 /**
341 * Implements hook_process_HOOK().
342 *
343 * Prevent caching pages with status messages on them. Note that due to the
344 * fact the messages are only added in template_process_page, we also need to
345 * use the process-hook.
346 */
347 function authcache_process_page(&$variables) {
348 if (!empty($variables['messages']) && authcache_page_is_cacheable()) {
349 authcache_cancel(t('Status message on page'));
350 }
351 }
352
353 //
354 // API for other modules.
355 //
356
357 /**
358 * Private function called from authcache_init. Authcache should not alter any
359 * aspect of this page.
360 */
361 function _authcache_exclude($reason = NULL) {
362 // No need for drupal_static here, flag may not be reset anyway.
363 static $excluded = FALSE;
364
365 if (!$excluded && !empty($reason)) {
366 $excluded = TRUE;
367 module_invoke_all('authcache_excluded', $reason);
368 }
369
370 return $excluded;
371 }
372
373 /**
374 * Return true if this page is excluded from page caching.
375 */
376 function authcache_excluded() {
377 return _authcache_exclude();
378 }
379
380 /**
381 * Prevent this page of beeing stored in the cache after it is built up.
382 */
383 function authcache_cancel($reason = NULL) {
384 // No need for drupal_static here, flag may not be reset anyway.
385 static $cancelled = FALSE;
386
387 if (!$cancelled && !empty($reason)) {
388 $cancelled = TRUE;
389 module_invoke_all('authcache_cancelled', $reason);
390 }
391
392 return $cancelled;
393 }
394
395 /**
396 * Return true if the caching of the page request was cancelled during
397 * page-build.
398 */
399 function authcache_cancelled() {
400 return authcache_cancel();
401 }
402
403 /**
404 * Return true if this page possibly will be cached later.
405 */
406 function authcache_page_is_cacheable() {
407 return !(authcache_excluded() || authcache_cancelled());
408 }
409
410 /**
411 * Return true if the given account is cacheable.
412 */
413 function authcache_account_allows_caching($account = NULL) {
414 global $user;
415 $cacheable = &drupal_static(__FUNCTION__);
416
417 if (!isset($account)) {
418 $account = $user;
419 }
420
421 if (!isset($cacheable[$account->uid])) {
422 $reasons = module_invoke_all('authcache_account_exclude', $account);
423 $cacheable[$account->uid] = empty($reasons);
424 }
425
426 return $cacheable[$account->uid];
427 }
428
429 /**
430 * Return characterizing key-value pairs of a browsers capabilities and the
431 * HTTP request.
432 */
433 function authcache_request_properties() {
434 static $properties;
435
436 if (!isset($properties)) {
437 $properties = module_invoke_all('authcache_request_properties');
438 drupal_alter('authcache_request_properties', $properties);
439 }
440
441 return $properties;
442 }
443
444 /**
445 * Return characterizing properties of groups the given account is a member of.
446 */
447 function authcache_account_properties($account = NULL) {
448 global $user;
449 static $properties;
450
451 if (!isset($account)) {
452 $account = $user;
453 }
454
455 if (!isset($properties)) {
456 $properties = module_invoke_all('authcache_account_properties', $account);
457 drupal_alter('authcache_account_properties', $properties, $account);
458 }
459
460 return $properties;
461 }
462
463 /**
464 * Return the property value of the given request property or null.
465 *
466 * @see hook_authcache_request_properties().
467 */
468 function authcache_get_request_property($name) {
469 $properties = authcache_request_properties();
470 return isset($properties[$name]) ? $properties[$name] : NULL;
471 }
472
473 /**
474 * Return the property value of the given account property or null.
475 *
476 * @see hook_authcache_account_properties().
477 */
478 function authcache_get_account_property($name, $account = NULL) {
479 $properties = authcache_account_properties($account);
480 return isset($properties[$name]) ? $properties[$name] : NULL;
481 }
482
483 /**
484 * Return the properties used as a base for calculation of the authcache key.
485 *
486 * @see authcache_key().
487 * @see hook_authcache_key_properties_alter().
488 */
489 function authcache_key_properties($account = NULL) {
490 global $user;
491
492 if (!isset($account)) {
493 $account = $user;
494 }
495
496 $properties = array(
497 'request' => authcache_request_properties(),
498 'account' => authcache_account_properties($account),
499 );
500 drupal_alter('authcache_key_properties', $properties, $account);
501
502 return $properties;
503 }
504
505 /**
506 * Generate and return the authcache key for the given account.
507 *
508 * @see hook_authcache_key_properties().
509 * @see hook_authcache_key_properties_alter().
510 */
511 function authcache_key($account = NULL) {
512 global $base_root, $user;
513
514 if (!isset($account)) {
515 $account = $user;
516 }
517
518 if ($account->uid) {
519 // Calculate the key for logged in users from key-properties.
520 $data = serialize(authcache_key_properties($account));
521 $hmac = hash_hmac('sha1', $data, drupal_get_private_key(), FALSE);
522
523 $abbrev = variable_get('authcache_hmac_abbrev', 7);
524 $key = $abbrev ? substr($hmac, 0, $abbrev) : $hmac;
525 }
526 else {
527 // Generate base-key for anonymous users.
528 $generator = variable_get('authcache_key_generator');
529 if (is_callable($generator)) {
530 $key = call_user_func($generator);
531 }
532 else {
533 $key = $base_root;
534 }
535 }
536
537 return $key;
538 }
539
540 /**
541 * Return the authcache cache-id for the given path.
542 *
543 * @see authcache_key().
544 */
545 function authcache_cid($request_uri = NULL, $account = NULL) {
546 if (!isset($request_uri)) {
547 $request_uri = request_uri();
548 }
549
550 $key = authcache_key($account);
551 return $key . $request_uri;
552 }
553
554 /**
555 * Add and remove cookies to the browser session as required.
556 *
557 * @see hook_authcache_cookie().
558 * @see hook_authcache_cookie_alter().
559 */
560 function authcache_fix_cookies($flags, $account = NULL) {
561 global $user;
562
563 if (!isset($account)) {
564 $account = $user;
565 }
566
567 $cookies = module_invoke_all('authcache_cookie', $flags, $account);
568 drupal_alter('authcache_cookie', $cookies, $flags, $account);
569
570 $default_params = array(
571 'present' => FALSE,
572 'value' => NULL,
573 'lifetime' => ini_get('session.cookie_lifetime'),
574 'path' => ini_get('session.cookie_path'),
575 'domain' => ini_get('session.cookie_domain'),
576 'secure' => ini_get('session.cookie_secure') == '1',
577 'httponly' => FALSE,
578 );
579
580 foreach ($cookies as $name => $params) {
581 $params += $default_params;
582
583 if ($params['present']) {
584 // Fix cookie if it is not present in the users browser or the value does
585 // not match our expectations.
586 if (!isset($_COOKIE[$name]) || $_COOKIE[$name] != $params['value']) {
587 $expires = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
588 setcookie($name, $params['value'], $expires, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
589 }
590 }
591 elseif (!$params['present'] && isset($_COOKIE[$name])) {
592 // Remove spare cookie
593 setcookie($name, "", REQUEST_TIME - 86400, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
594 }
595 }
596 }
597
598 /**
599 * Returns an array containing all the roles from account_roles that are not
600 * present in allowed_roles.
601 */
602 function authcache_diff_roles($account_roles, $allowed_roles) {
603 // Remove "authenticated user"-role from the account roles except when it is
604 // the only role on the account.
605 if (array_keys($account_roles) != array(DRUPAL_AUTHENTICATED_RID)) {
606 unset($account_roles[DRUPAL_AUTHENTICATED_RID]);
607 }
608
609 return array_diff_key($account_roles, $allowed_roles);
610 }
611
612 /**
613 * Determines the MIME content type of the current page response based on
614 * the currently set Content-Type HTTP header.
615 *
616 * This should normally return the string 'text/html' unless another module
617 * has overridden the content type.
618 */
619 function _authcache_get_content_type($default = NULL) {
620 $params = explode(';', drupal_get_http_header('content-type'));
621 $params = array_map('trim', $params);
622 $mime = array_shift($params);
623
624 return array(
625 'mimetype' => $mime,
626 'params' => $params,
627 );
628 }
629
630 /**
631 * Determines the HTTP response code that the current page request will be
632 * returning by examining the HTTP headers that have been output so far.
633 */
634 function _authcache_get_http_status($status = 200) {
635 $value = drupal_get_http_header('status');
636 return isset($value) ? (int) $value : $status;
637 }
638
639 /**
640 * Stores the current page in the cache.
641 *
642 * @see hook_authcache_presave().
643 * @see hook_authcache_cache_alter().
644 * @see drupal_page_set_cache().
645 */
646 function authcache_page_set_cache() {
647 // Give other modules a last chance to cancel page saving
648 module_invoke_all('authcache_presave');
649
650 if (authcache_page_is_cacheable()) {
651 $cache = (object) array(
652 'cid' => authcache_cid(),
653 'data' => array(
654 'path' => $_GET['q'],
655 'body' => ob_get_clean(),
656 'title' => drupal_get_title(),
657 'headers' => array(),
658 ),
659 'expire' => CACHE_TEMPORARY,
660 'created' => REQUEST_TIME,
661 );
662
663 // Restore preferred header names based on the lower-case names returned
664 // by drupal_get_http_header().
665 $header_names = _drupal_set_preferred_header_name();
666 foreach (drupal_get_http_header() as $name_lower => $value) {
667 $cache->data['headers'][$header_names[$name_lower]] = $value;
668 if ($name_lower == 'expires') {
669 // Use the actual timestamp from an Expires header if available.
670 $cache->expire = strtotime($value);
671 }
672 }
673
674 if ($cache->data['body']) {
675 if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
676 $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
677 }
678
679 // Let other modules act on the cacheable data.
680 drupal_alter('authcache_cache', $cache);
681
682 cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
683 }
684 return $cache;
685 }
686 }
687
688 //
689 // Authcache hooks
690 //
691
692 /**
693 * Implements hook_authcache_request_properties().
694 */
695 function authcache_authcache_request_properties() {
696 global $base_url;
697
698 return array(
699 'js' => !empty($_COOKIE['has_js']),
700 'base_url' => $base_url,
701 );
702 }
703
704 /**
705 * Implements hook_authcache_account_properties().
706 */
707 function authcache_authcache_account_properties($account) {
708 $roles = array_keys($account->roles);
709 sort($roles);
710
711 return array(
712 'roles' => $roles,
713 );
714 }
715
716 /**
717 * Implements hook_authcache_request_exclude().
718 */
719 function authcache_authcache_request_exclude() {
720 global $user;
721
722 // The following three basic exclusion rules are mirrored in
723 // authcacheinc_retrieve_cache_page() in authcache.inc
724 // BEGIN: basic exclusion rules
725 if (drupal_is_cli()) {
726 return t('Running as CLI script');
727 }
728
729 if (!($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) {
730 return t('Only GET and HEAD requests allowed. Method for this request is: @method.',
731 array('@method' => $_SERVER['REQUEST_METHOD']));
732 }
733
734 if (($ar = explode('?', basename(request_uri()))) && substr(array_shift($ar), -4) == '.php') {
735 return t('PHP files (cron.php, update.php, etc)');
736 }
737 // END: basic exclusion rules
738
739 module_load_install('authcache');
740 $requirements = module_invoke('authcache', 'requirements', 'runtime');
741 if (isset($requirements['authcache']['severity']) && $requirements['authcache']['severity'] == REQUIREMENT_ERROR) {
742 return $requirements['authcache']['description'];
743 }
744
745 if (variable_get('authcache_noajax', FALSE)
746 && isset($_SERVER['HTTP_X_REQUESTED_WITH'])
747 && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'
748 ) {
749 return t('Ajax request');
750 }
751
752 $alias = drupal_get_path_alias($_GET['q']);
753
754 // Now check page caching settings, defined by the site admin
755 $pagecaching = variable_get('authcache_pagecaching', array(array(
756 'option' => 0,
757 'pages' => AUTHCACHE_NOCACHE_DEFAULT,
758 'roles' => array(DRUPAL_ANONYMOUS_RID),
759 )));
760
761 foreach ($pagecaching as $i => $page_rules) {
762 // Do caching page roles apply to current user?
763 $extra_roles = authcache_diff_roles($user->roles, $page_rules['roles']);
764 if (empty($extra_roles)) {
765 switch ($page_rules['option']) {
766 case '0': // Cache every page except the listed pages.
767 case '1': // Cache only the listed pages.
768 $page_listed = drupal_match_path($alias, $page_rules['pages']);
769 if (!(!($page_rules['option'] xor $page_listed))) {
770 return t('Caching disabled by path list of page ruleset #@number', array('@number' => $i));
771 }
772 break;
773
774 case '2': // Cache pages for which the following PHP code returns TRUE
775 $result = 0;
776 if (module_exists('php')) {
777 $result = php_eval($page_rules['pages']);
778 }
779 if (empty($result)) {
780 return t('Caching disabled by PHP rule of page ruleset #@number', array('@number' => $i));
781 }
782 break;
783
784 default:
785 break;
786 }
787
788 if (!empty($page_rules['noadmin']) && path_is_admin(current_path())) {
789 return t('Not caching admin pages (by page ruleset #@number)', array('@number' => $i));
790 }
791 }
792 }
793 }
794
795
796 /**
797 * Implements hook_authcache_account_exclude().
798 */
799 function authcache_authcache_account_exclude($account) {
800 // Bail out from requests by superuser (uid=1)
801 if ($account->uid == 1 && !variable_get('authcache_su', 0)) {
802 return t('Caching disabled for superuser');
803 }
804
805 // Check for non-cacheable roles of the account.
806 $cache_roles = variable_get('authcache_roles', array());
807 $extra_roles = authcache_diff_roles($account->roles, $cache_roles);
808 if (!empty($extra_roles)) {
809 return format_plural(count($extra_roles),
810 'Account has non-cachable role @roles',
811 'Account has non-cachable roles @roles',
812 array('@roles' => implode(', ', $extra_roles)));
813 }
814
815 // If JavaScript is disabled on the users browser, check if chaching is still
816 // allowed.
817 if (!authcache_get_request_property('js')) {
818 $nojs_roles = variable_get('authcache_nojsroles', drupal_map_assoc(array(DRUPAL_ANONYMOUS_RID)));
819 $extra_roles = authcache_diff_roles($account->roles, $nojs_roles);
820 if (!empty($extra_roles)) {
821 return format_plural(count($extra_roles),
822 'Role @roles is not cacheable if JavaScript is disabled.',
823 'Roles @roles are not cacheable if JavaScript is disabled.',
824 array('@roles' => implode(', ', $extra_roles)));
825 }
826 }
827 }
828
829
830 /**
831 * Implements hook_authcache_presave().
832 */
833 function authcache_authcache_presave() {
834 // Check content-type
835 $content_type = _authcache_get_content_type();
836 $allowed_mimetypes = preg_split('/(\r\n?|\n)/', variable_get('authcache_mimetype', AUTHCACHE_MIMETYPE_DEFAULT), -1, PREG_SPLIT_NO_EMPTY);
837 if (!in_array($content_type['mimetype'], $allowed_mimetypes)) {
838 authcache_cancel(t('Only cache allowed HTTP content types (HTML, JS, etc)'));
839 }
840
841 // Check http status
842 if (variable_get('authcache_http200', FALSE) && _authcache_get_http_status() != 200) {
843 authcache_cancel(t('Don`t cache 404/403s/etc'));
844 }
845
846 // Check headers already were sent
847 if (headers_sent()) {
848 authcache_cancel(t('Don`t cache private file transfers or if headers were unexpectly sent.'));
849 }
850
851 // Make sure "Location" redirect isn't used
852 foreach (headers_list() as $header) {
853 if (strpos($header, 'Location:') === 0) {
854 authcache_cancel(t('Location header detected'));
855 }
856 }
857
858 // Don't cache pages with PHP errors (Drupal can't catch fatal errors)
859 if (function_exists('error_get_last') && $error = error_get_last()) {
860 switch ($error['type']) {
861 // Ignore these errors:
862 case E_NOTICE: // run-time notices
863 case E_USER_NOTICE: // user-generated notice message
864 case E_DEPRECATED: // run-time notices
865 case E_USER_DEPRECATED: // user-generated notice message
866 break;
867 default:
868 // Let user know there is PHP error and return
869 authcache_cancel(t('PHP Error: @error', array('@error' => error_get_last())));
870 break;
871 }
872 }
873 }
874
875
876 /**
877 * Implements hook_authcache_cookie().
878 */
879 function authcache_authcache_cookie($flags, $account) {
880 $authenticated = $account->uid;
881 $enabled = $flags & AUTHCACHE_FLAGS_ACCOUNT_ENABLED;
882 $present = $authenticated && $enabled;
883
884 $cookies['authcache']['present'] = $present;
885 $cookies['authcache']['httponly'] = TRUE;
886 $cookies['drupal_user']['present'] = $present;
887 $cookies['drupal_uid']['present'] = $present;
888
889 if ($present) {
890 $cookies['authcache']['value'] = authcache_key($account);
891 $cookies['drupal_user']['value'] = $account->name;
892 $cookies['drupal_uid']['value'] = $account->uid;
893 }
894
895 return $cookies;
896 }
897
898 /**
899 * Implements hook_aceajax_request().
900 */
901 function authcache_aceajax_request() {
902 $request['tab'] = array(
903 'maxage' => 86400,
904 );
905
906 return $request;
907 }
908
909 /**
910 * Implements hook_aceajax_command().
911 */
912 function authcache_aceajax_command() {
913 return array(
914 'form_token_id' => array(
915 'class' => 'AuthcacheFormTokenIdCommand',
916 ),
917 'menu_local_tasks' => array(
918 'class' => 'AuthcacheMenuLocalTasksCommand',
919 'bootstrap' => DRUPAL_BOOTSTRAP_FULL,
920 ),
921 );
922 }