From 5a8b145b7faa8e89fa15518fe9eb46240a288dd3 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 18 Jan 2014 17:43:15 +0000 Subject: [PATCH] fixed caching in a couple of places: - user.php was not reloaded if (session-)preferences changed eg. language via select-box in login, because we used Expires header, but did not force a different url - (user|config|images).php now has etag on url, to force reload by browser as we use an Expires header (changed images still need Admin >> clear cache to rebuild image cache) - preferences are now loaded via a cachable GET request --- phpgwapi/config.php | 11 ++++---- phpgwapi/images.php | 11 ++++---- phpgwapi/inc/class.egw_framework.inc.php | 36 ++++++++++++++++++++---- phpgwapi/inc/class.preferences.inc.php | 14 +++++---- phpgwapi/js/jsapi/egw_json.js | 6 ++-- phpgwapi/js/jsapi/egw_preferences.js | 4 +-- phpgwapi/lang.php | 9 ++---- phpgwapi/user.php | 5 ++-- 8 files changed, 57 insertions(+), 39 deletions(-) diff --git a/phpgwapi/config.php b/phpgwapi/config.php index 92290bcb9f..c0b4782a07 100644 --- a/phpgwapi/config.php +++ b/phpgwapi/config.php @@ -25,14 +25,13 @@ $GLOBALS['egw_info'] = array( include '../header.inc.php'; // use an etag over config and link-registry -$config = config::clientConfigs(); +$config = json_encode(config::clientConfigs()); $link_registry = egw_link::json_registry(); -$etag = '"'.md5(serialize($config).$link_registry).'"'; +$etag = '"'.md5($config.$link_registry).'"'; -// headers to allow caching +// headers to allow caching, egw_framework specifies etag on url to force reload, even with Expires header +egw_session::cache_control(86400); // cache for one day Header('Content-Type: text/javascript; charset=utf-8'); -Header('Cache-Control: public, no-transform'); -Header('Pragma: cache'); Header('ETag: '.$etag); // if servers send a If-None-Match header, response with 304 Not Modified, if etag matches @@ -42,7 +41,7 @@ if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $ common::egw_exit(); } -$content = 'egw.set_configs('.json_encode($config).");\n"; +$content = 'egw.set_configs('.$config.");\n"; $content .= 'egw.set_link_registry('.$link_registry.");\n"; // we run our own gzip compression, to set a correct Content-Length of the encoded content diff --git a/phpgwapi/images.php b/phpgwapi/images.php index 61c136e2f8..e158f34870 100644 --- a/phpgwapi/images.php +++ b/phpgwapi/images.php @@ -24,15 +24,14 @@ $GLOBALS['egw_info'] = array( include '../header.inc.php'; -$content = common::image_map(preg_match('/^[a-z0-9_-]+$/i',$_GET['template']) ? $_GET['template'] : null, $_GET['svg']); +$content = json_encode(common::image_map(preg_match('/^[a-z0-9_-]+$/i',$_GET['template']) ? $_GET['template'] : null, $_GET['svg'])); // use an etag over the image mapp -$etag = '"'.md5(serialize($content)).'"'; +$etag = '"'.md5($content).'"'; -// headers to allow caching +// headers to allow caching, egw_framework specifies etag on url to force reload, even with Expires header +egw_session::cache_control(86400); // cache for one day Header('Content-Type: text/javascript; charset=utf-8'); -Header('Cache-Control: public, no-transform'); -Header('Pragma: cache'); Header('ETag: '.$etag); // if servers send a If-None-Match header, response with 304 Not Modified, if etag matches @@ -42,7 +41,7 @@ if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $ common::egw_exit(); } -$content = 'egw.set_images('.json_encode($content).");\n"; +$content = 'egw.set_images('.$content.");\n"; // we run our own gzip compression, to set a correct Content-Length of the encoded content if (in_array('gzip', explode(',',$_SERVER['HTTP_ACCEPT_ENCODING'])) && function_exists('gzencode')) diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index d2a92ef31f..8d04c9be58 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -1125,11 +1125,23 @@ abstract class egw_framework $java_script .= $GLOBALS['egw_info']['flags']['java_script_thirst'] . "\n"; } // add configuration, link-registry, images, user-data and -perferences for non-popup windows + // specifying etag in url to force reload, as we send expires header if ($GLOBALS['egw_info']['flags']['js_link_registry']) { - self::validate_file('/phpgwapi/config.php'); - self::validate_file('/phpgwapi/images.php',array('template' => $GLOBALS['egw_info']['user']['preferences']['common']['template_set'])); - self::validate_file('/phpgwapi/user.php',array('user' => $GLOBALS['egw_info']['user']['account_lid'])); + self::validate_file('/phpgwapi/config.php', array( + 'etag' => md5(json_encode(config::clientConfigs()).egw_link::json_registry()), + )); + self::validate_file('/phpgwapi/images.php', array( + 'template' => $GLOBALS['egw_info']['user']['preferences']['common']['template_set'], + 'etag' => md5(json_encode(common::image_map($GLOBALS['egw_info']['user']['preferences']['common']['template_set']))), + )); + self::validate_file('/phpgwapi/user.php', array( + 'user' => $GLOBALS['egw_info']['user']['account_lid'], + 'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'], + // add etag on url, so we can set an expires header + 'etag' => md5(json_encode($GLOBALS['egw_info']['user']['preferences']['common']). + $GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id'])), + )); } $extra['url'] = $GLOBALS['egw_info']['server']['webserver_url']; @@ -1870,9 +1882,21 @@ abstract class egw_framework if (preg_match('/^[a-z0-9_]+$/i', $app)) { $response = egw_json_response::get(); - $pref = $GLOBALS['egw_info']['user']['preferences'][$app]; - if(!$pref) $pref = Array(); - $response->script('window.egw.set_preferences('.json_encode($pref).', "'.$app.'");'); + $pref = json_encode($GLOBALS['egw_info']['user']['preferences'][$app] ? + $GLOBALS['egw_info']['user']['preferences'][$app] : array()); + + // send etag header, if we are directly called (not via jsonq!) + if (strpos($_GET['menuaction'], __FUNCTION__) !== false) + { + $etag = '"'.$app.'-'.md5($pref).'"'; + if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) + { + header("HTTP/1.1 304 Not Modified"); + common::egw_exit(); + } + header('ETag: '.$etag); + } + $response->script('window.egw.set_preferences('.$pref.', "'.$app.'");'); } } diff --git a/phpgwapi/inc/class.preferences.inc.php b/phpgwapi/inc/class.preferences.inc.php index 9e07d1b2a7..49d046d6ed 100644 --- a/phpgwapi/inc/class.preferences.inc.php +++ b/phpgwapi/inc/class.preferences.inc.php @@ -15,12 +15,14 @@ /** * preferences class used for setting application preferences * - * the prefs are read into 5 arrays: - * $data the effective prefs used everywhere in phpgw, they are merged from the other 3 arrays - * $user the stored user prefs, only used for manipulating and storeing the user prefs - * $group the stored prefs of all group-memberships of current user, can NOT be deleted or stored directly! - * $default the default preferences, always used when the user has no own preference set - * $forced forced preferences set by the admin, they take precedence over user or default prefs + * preferences are read into following arrays: + * - $data effective prefs used everywhere in EGroupware + * Effective prefs are merged together in following precedence from: + * - $forced forced preferences set by the admin, they take precedence over user or default prefs + * - $session temporary prefs eg. language set on login just for session + * - $user the stored user prefs, only used for manipulating and storeing the user prefs + * - $group the stored prefs of all group-memberships of current user, can NOT be deleted or stored directly! + * - $default the default preferences, always used when the user has no own preference set * * To update the prefs of a certain group, not just the primary group of the user, you have to * create a new instance of preferences class, with the given id of the group. This takes into diff --git a/phpgwapi/js/jsapi/egw_json.js b/phpgwapi/js/jsapi/egw_json.js index a6774b53bc..978594826f 100644 --- a/phpgwapi/js/jsapi/egw_json.js +++ b/phpgwapi/js/jsapi/egw_json.js @@ -69,9 +69,9 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd) { /** * Sends the assembled request to the server * @param {boolean} [async=false] Overrides async provided in constructor to give an easy way to make simple async requests - * @returns undefined + * @param {string} method='POST' allow to eg. use a (cachable) 'GET' request instead of POST */ - json_request.prototype.sendRequest = function(async) { + json_request.prototype.sendRequest = function(async,method) { if(typeof async != "undefined") { this.async = async; @@ -95,7 +95,7 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd) { context: this, data: request_obj, dataType: 'json', - type: 'POST', + type: method || 'POST', success: this.handleResponse, error: function(_xmlhttp, _err) { this.egw.debug('error', 'Ajax request to', this.url, ' failed:', diff --git a/phpgwapi/js/jsapi/egw_preferences.js b/phpgwapi/js/jsapi/egw_preferences.js index e0029890ca..19179938eb 100644 --- a/phpgwapi/js/jsapi/egw_preferences.js +++ b/phpgwapi/js/jsapi/egw_preferences.js @@ -62,8 +62,8 @@ egw.extend('preferences', egw.MODULE_GLOBAL, function() { if (typeof prefs[_app] == 'undefined') { - var request = this.json('home.egw_framework.ajax_get_preference.template', [_app],null,null,false); - request.sendRequest(); + var request = this.json('home.egw_framework.ajax_get_preference.template', [_app]); + request.sendRequest(false, 'GET'); // use synchronous (cachable) GET request if (typeof prefs[_app] == 'undefined') prefs[_app] = {}; } if(_name == "*") return prefs[_app]; diff --git a/phpgwapi/lang.php b/phpgwapi/lang.php index 1f9ee60137..35c344b05e 100644 --- a/phpgwapi/lang.php +++ b/phpgwapi/lang.php @@ -32,14 +32,9 @@ include '../header.inc.php'; // use an etag with app, lang and a hash over the creation-times of all lang-files $etag = '"'.$_GET['app'].'-'.$_GET['lang'].'-'.translation::etag($_GET['app'], $_GET['lang']).'"'; -// tell browser/caches to cache for one day, we change url on real modifications -$expires = 864000; // 10days - -// headers to allow caching of one month +// headers to allow caching, we specify etag on url to force reload, even with Expires header +egw_session::cache_control(864000); // cache for 10 days Header('Content-Type: text/javascript; charset=utf-8'); -Header('Cache-Control: public, no-transform, max-age='.$expires); -header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT'); -Header('Pragma: cache'); Header('ETag: '.$etag); // if servers send a If-None-Match header, response with 304 Not Modified, if etag matches diff --git a/phpgwapi/user.php b/phpgwapi/user.php index f8037e3139..0b06f51a7a 100644 --- a/phpgwapi/user.php +++ b/phpgwapi/user.php @@ -29,10 +29,9 @@ $preferences = json_encode($GLOBALS['egw_info']['user']['preferences']['common'] $user = $GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id']); $etag = '"'.md5($preferences.$user).'"'; -// headers to allow caching +// headers to allow caching, egw_framework specifies etag on url to force reload, even with Expires header +egw_session::cache_control(86400); // cache for 1 day Header('Content-Type: text/javascript; charset=utf-8'); -Header('Cache-Control: public, no-transform'); -Header('Pragma: cache'); Header('ETag: '.$etag); // if servers send a If-None-Match header, response with 304 Not Modified, if etag matches