Commit 19707a63 authored by totten's avatar totten

(#174) Forms/Sessions - Store state via Civi::cache('session')

Overview
----------------------------------------

When using forms based on CiviQuickForm (specifically `CRM_Core_Controller`), `CRM_Core_Session`
stores form-state via `CRM_Core_BAO_Cache::storeSessionToCache` and `::restoreSessionFromCache`,
which in turn calls `CRM_Core_BAO_Cache::setItem()` and `::getItem()`.

However, using `CRM_Core_BAO_Cache::setItem()` and `::getItem()` means that all session state
**must** be written to MySQL.  For #174, we seek the **option** to store via
Redis/Memcache.

Before
----------------------------------------

* (a) Form/session state is always stored via `CRM_Core_BAO_Cache::setItem()` and `civicrm_cache` table.
* (b) To ensure that old sessions are periodically purged, there is special purpose logic that accesses `civicrm_cache`
  (roughly `delete where group_name=Sessions and  created_date < now()-ttl`).
* (c) On Memcache/Redis-enabled systems, the cache server functions as an extra tier. The DB provides canonical storage for form/session state.

After
----------------------------------------

* (a) Form/session state is stored via `CRM_Utils_CacheInterface`.
    * On a typical server, this defaults to `CRM_Utils_Cache_SqlGroup` and `civicrm_cache` table.
* (b) To ensure that old sessions are periodically purged, the call to `CRM_Utils_CacheInterface::set()` specifies a TTL.
    * It is the responsibility of the cache driver to handle TTLs. With #12360, TTL's are supported in `ArrayCache`, `SqlGroup`, and `Redis`.
* (c) On Memcache/Redis-enabled systems, the cache server provides canonical storage for form/session state.
parent 3962e60c
......@@ -40,6 +40,13 @@
*/
class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
/**
* When store session/form state, how long should the data be retained?
*
* @var int, number of second
*/
const DEFAULT_SESSION_TTL = 172800; // Two days: 2*24*60*60
/**
* @var array ($cacheKey => $cacheValue)
*/
......@@ -237,7 +244,8 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
$value = $_SESSION[$sessionName[0]][$sessionName[1]];
}
self::setItem($value, 'CiviCRM Session', "{$sessionName[0]}_{$sessionName[1]}");
$key = "{$sessionName[0]}_{$sessionName[1]}";
Civi::cache('session')->set($key, $value, self::pickSessionTtl($key));
if ($resetSession) {
$_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
unset($_SESSION[$sessionName[0]][$sessionName[1]]);
......@@ -248,7 +256,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
if (!empty($_SESSION[$sessionName])) {
$value = $_SESSION[$sessionName];
}
self::setItem($value, 'CiviCRM Session', $sessionName);
Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName));
if ($resetSession) {
$_SESSION[$sessionName] = NULL;
unset($_SESSION[$sessionName]);
......@@ -275,17 +283,13 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
public static function restoreSessionFromCache($names) {
foreach ($names as $key => $sessionName) {
if (is_array($sessionName)) {
$value = self::getItem('CiviCRM Session',
"{$sessionName[0]}_{$sessionName[1]}"
);
$value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}");
if ($value) {
$_SESSION[$sessionName[0]][$sessionName[1]] = $value;
}
}
else {
$value = self::getItem('CiviCRM Session',
$sessionName
);
$value = Civi::cache('session')->get($sessionName);
if ($value) {
$_SESSION[$sessionName] = $value;
}
......@@ -293,6 +297,32 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
}
}
/**
* Determine how long session-state should be retained.
*
* @param string $sessionKey
* Ex: '_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654_container'
* Ex: 'CiviCRM_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654'
* @return int
* Number of seconds.
*/
protected static function pickSessionTtl($sessionKey) {
$secureSessionTimeoutMinutes = (int) Civi::settings()->get('secure_cache_timeout_minutes');
if ($secureSessionTimeoutMinutes) {
$transactionPages = array(
'CRM_Contribute_Controller_Contribution',
'CRM_Event_Controller_Registration',
);
foreach ($transactionPages as $transactionPage) {
if (strpos($sessionKey, $transactionPage) !== FALSE) {
return $secureSessionTimeoutMinutes * 60;
}
}
}
return self::DEFAULT_SESSION_TTL;
}
/**
* Do periodic cleanup of the CiviCRM session table.
*
......@@ -305,32 +335,6 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
* @param bool $prevNext
*/
public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) {
// first delete all sessions more than 20 minutes old which are related to any potential transaction
$timeIntervalMins = (int) Civi::settings()->get('secure_cache_timeout_minutes');
if ($timeIntervalMins && $session) {
$transactionPages = array(
'CRM_Contribute_Controller_Contribution',
'CRM_Event_Controller_Registration',
);
$params = array(
1 => array(
date('Y-m-d H:i:s', time() - $timeIntervalMins * 60),
'String',
),
);
foreach ($transactionPages as $trPage) {
$params[] = array("%${trPage}%", 'String');
$where[] = 'path LIKE %' . count($params);
}
$sql = "
DELETE FROM civicrm_cache
WHERE group_name = 'CiviCRM Session'
AND created_date <= %1
AND (" . implode(' OR ', $where) . ")";
CRM_Core_DAO::executeQuery($sql, $params);
}
// clean up the session cache every $cacheCleanUpNumber probabilistically
$cleanUpNumber = 757;
......@@ -355,13 +359,8 @@ AND (" . implode(' OR ', $where) . ")";
}
if ($session) {
$sql = "
DELETE FROM civicrm_cache
WHERE group_name = 'CiviCRM Session'
AND created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
";
CRM_Core_DAO::executeQuery($sql);
// Session caches are just regular caches, so they expire naturally per TTL.
$expired = TRUE;
}
if ($expired) {
......
......@@ -286,7 +286,7 @@ class CRM_Core_Session {
$values = &$this->_session[$this->_key];
}
else {
$values = CRM_Core_BAO_Cache::getItem('CiviCRM Session', "CiviCRM_{$prefix}");
$values = Civi::cache('session')->get("CiviCRM_{$prefix}");
}
if ($values) {
......
......@@ -164,6 +164,7 @@ class Container {
'js_strings' => 'js_strings',
'community_messages' => 'community_messages',
'checks' => 'checks',
'session' => 'CiviCRM Session',
);
foreach ($basicCaches as $cacheSvc => $cacheGrp) {
$container->setDefinition("cache.{$cacheSvc}", new Definition(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment