(dev/core#174) Forms/Sessions - Store state via Civi::cache('session')
authorTim Otten <totten@civicrm.org>
Thu, 29 Mar 2018 01:52:57 +0000 (18:52 -0700)
committerTim Otten <totten@civicrm.org>
Mon, 2 Jul 2018 05:08:23 +0000 (22:08 -0700)
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 dev/core#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.

CRM/Core/BAO/Cache.php
CRM/Core/Session.php
Civi/Core/Container.php

index a8bde2f16b3d3fd152acb41d7a8deee024766e0f..ce1d63ede35a9a3106ea2f10f13963330fc5645d 100644 (file)
  */
 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) {
index ccadd74bcda457240cbbfc7d97c12a90654b3a52..b81cc961f0f96b95a715fc3ea6f9a2b47a50f27f 100644 (file)
@@ -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) {
index a137d166b56a0567df3077db50c0504eebe5f202..bb6ca79319606fcc49787dd30383b703b7f3e54e 100644 (file)
@@ -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(