$cacheValue) * * @var array */ public static $_cache = NULL; /** * Retrieve an item from the DB cache. * * @param string $group * (required) The group name of the item. * @param string $path * (required) The path under which this item is stored. * @param int $componentID * The optional component ID (so componenets can share the same name space). * * @return object * The data if present in cache, else null * @deprecated */ public static function &getItem($group, $path, $componentID = NULL) { CRM_Core_Error::deprecatedFunctionWarning( 'CRM_Core_BAO_Cache::getItem is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' ); if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { $value = $adapter::getItem($group, $path, $componentID); return $value; } if (self::$_cache === NULL) { self::$_cache = []; } $argString = "CRM_CT_{$group}_{$path}_{$componentID}"; if (!array_key_exists($argString, self::$_cache)) { $cache = CRM_Utils_Cache::singleton(); $cleanKey = self::cleanKey($argString); self::$_cache[$argString] = $cache->get($cleanKey); if (self::$_cache[$argString] === NULL) { $table = self::getTableName(); $where = self::whereCache($group, $path, $componentID); $rawData = CRM_Core_DAO::singleValueQuery("SELECT data FROM $table WHERE $where"); $data = $rawData ? self::decode($rawData) : NULL; self::$_cache[$argString] = $data; if ($data !== NULL) { // Do not cache 'null' as that is most likely a cache miss & we shouldn't then cache it. $cache->set($cleanKey, self::$_cache[$argString]); } } } return self::$_cache[$argString]; } /** * Retrieve all items in a group. * * @param string $group * (required) The group name of the item. * @param int $componentID * The optional component ID (so componenets can share the same name space). * * @return object * The data if present in cache, else null * @deprecated */ public static function &getItems($group, $componentID = NULL) { CRM_Core_Error::deprecatedFunctionWarning( 'CRM_Core_BAO_Cache::getItems is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' ); if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { return $adapter::getItems($group, $componentID); } if (self::$_cache === NULL) { self::$_cache = []; } $argString = "CRM_CT_CI_{$group}_{$componentID}"; if (!array_key_exists($argString, self::$_cache)) { $cache = CRM_Utils_Cache::singleton(); $cleanKey = self::cleanKey($argString); self::$_cache[$argString] = $cache->get($cleanKey); if (!self::$_cache[$argString]) { $table = self::getTableName(); $where = self::whereCache($group, NULL, $componentID); $dao = CRM_Core_DAO::executeQuery("SELECT path, data FROM $table WHERE $where"); $result = []; while ($dao->fetch()) { $result[$dao->path] = self::decode($dao->data); } self::$_cache[$argString] = $result; $cache->set($cleanKey, self::$_cache[$argString]); } } return self::$_cache[$argString]; } /** * Store an item in the DB cache. * * @param object $data * (required) A reference to the data that will be serialized and stored. * @param string $group * (required) The group name of the item. * @param string $path * (required) The path under which this item is stored. * @param int $componentID * The optional component ID (so componenets can share the same name space). * @deprecated */ public static function setItem(&$data, $group, $path, $componentID = NULL) { CRM_Core_Error::deprecatedFunctionWarning( 'CRM_Core_BAO_Cache::setItem is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' ); if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { return $adapter::setItem($data, $group, $path, $componentID); } if (self::$_cache === NULL) { self::$_cache = []; } // get a lock so that multiple ajax requests on the same page // dont trample on each other // CRM-11234 $lock = Civi::lockManager()->acquire("cache.{$group}_{$path}._{$componentID}"); if (!$lock->isAcquired()) { CRM_Core_Error::fatal(); } $table = self::getTableName(); $where = self::whereCache($group, $path, $componentID); $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}"); // FIXME - Use SQL NOW() or CRM_Utils_Time? $now = date('Y-m-d H:i:s'); $dataSerialized = self::encode($data); // This table has a wonky index, so we cannot use REPLACE or // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE). if ($dataExists) { $sql = "UPDATE $table SET data = %1, created_date = %2 WHERE {$where}"; $args = [ 1 => [$dataSerialized, 'String'], 2 => [$now, 'String'], ]; $dao = CRM_Core_DAO::executeQuery($sql, $args, TRUE, NULL, FALSE, FALSE); } else { $insert = CRM_Utils_SQL_Insert::into($table) ->row([ 'group_name' => $group, 'path' => $path, 'component_id' => $componentID, 'data' => $dataSerialized, 'created_date' => $now, ]); $dao = CRM_Core_DAO::executeQuery($insert->toSQL(), [], TRUE, NULL, FALSE, FALSE); } $lock->release(); // cache coherency - refresh or remove dependent caches $argString = "CRM_CT_{$group}_{$path}_{$componentID}"; $cache = CRM_Utils_Cache::singleton(); $data = self::decode($dataSerialized); self::$_cache[$argString] = $data; $cache->set(self::cleanKey($argString), $data); $argString = "CRM_CT_CI_{$group}_{$componentID}"; unset(self::$_cache[$argString]); $cache->delete(self::cleanKey($argString)); } /** * Delete all the cache elements that belong to a group OR delete the entire cache if group is not specified. * * @param string $group * The group name of the entries to be deleted. * @param string $path * Path of the item that needs to be deleted. * @param bool $clearAll clear all caches * @deprecated */ public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) { CRM_Core_Error::deprecatedFunctionWarning( 'CRM_Core_BAO_Cache::deleteGroup is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' ); if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { return $adapter::deleteGroup($group, $path); } else { $table = self::getTableName(); $where = self::whereCache($group, $path, NULL); CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where"); } if ($clearAll) { self::resetCaches(); } } /** * Cleanup ACL and System Level caches */ public static function resetCaches() { // also reset ACL Cache // @todo why is this called when CRM_Utils_System::flushCache() does it as well. CRM_ACL_BAO_Cache::resetCache(); // also reset memory cache if any CRM_Utils_System::flushCache(); } /** * The next two functions are internal functions used to store and retrieve session from * the database cache. This keeps the session to a limited size and allows us to * create separate session scopes for each form in a tab */ /** * This function takes entries from the session array and stores it in the cache. * * It also deletes the entries from the $_SESSION object (for a smaller session size) * * @param array $names * Array of session values that should be persisted. * This is either a form name + qfKey or just a form name * (in the case of profile) * @param bool $resetSession * Should session state be reset on completion of DB store?. */ public static function storeSessionToCache($names, $resetSession = TRUE) { foreach ($names as $key => $sessionName) { if (is_array($sessionName)) { $value = NULL; if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) { $value = $_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]]); } } else { $value = NULL; if (!empty($_SESSION[$sessionName])) { $value = $_SESSION[$sessionName]; } Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName)); if ($resetSession) { $_SESSION[$sessionName] = NULL; unset($_SESSION[$sessionName]); } } } self::cleanup(); } /* Retrieve the session values from the cache and populate the $_SESSION array * * @param array $names * Array of session values that should be persisted. * This is either a form name + qfKey or just a form name * (in the case of profile) */ /** * Restore session from cache. * * @param string $names */ public static function restoreSessionFromCache($names) { foreach ($names as $key => $sessionName) { if (is_array($sessionName)) { $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}"); if ($value) { $_SESSION[$sessionName[0]][$sessionName[1]] = $value; } } else { $value = Civi::cache('session')->get($sessionName); if ($value) { $_SESSION[$sessionName] = $value; } } } } /** * 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 = [ '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. * * Also delete all session cache entries which are a couple of days old. * This keeps the session cache to a manageable size * Delete Contribution page session caches more energetically. * * @param bool $session * @param bool $table * @param bool $prevNext * @param bool $expired */ public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) { // clean up the session cache every $cacheCleanUpNumber probabilistically $cleanUpNumber = 757; // clean up all sessions older than $cacheTimeIntervalDays days $timeIntervalDays = 2; if (mt_rand(1, 100000) % $cleanUpNumber == 0) { $expired = $session = $table = $prevNext = TRUE; } if (!$session && !$table && !$prevNext && !$expired) { return; } if ($prevNext) { // delete all PrevNext caches CRM_Core_BAO_PrevNextCache::cleanupCache(); } if ($table) { CRM_Core_Config::clearTempTables($timeIntervalDays . ' day'); } if ($session) { // Session caches are just regular caches, so they expire naturally per TTL. $expired = TRUE; } if ($expired) { $sql = "DELETE FROM civicrm_cache WHERE expired_date < %1"; $params = [ 1 => [date(CRM_Utils_Cache_SqlGroup::TS_FMT, CRM_Utils_Time::getTimeRaw()), 'String'], ]; CRM_Core_DAO::executeQuery($sql, $params); } } /** * (Quasi-private) Encode an object/array/string/int as a string. * * @param $mixed * @return string */ public static function encode($mixed) { return base64_encode(serialize($mixed)); } /** * (Quasi-private) Decode an object/array/string/int from a string. * * @param $string * @return mixed */ public static function decode($string) { // Upgrade support -- old records (serialize) always have this punctuation, // and new records (base64) never do. if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) { return unserialize($string); } else { return unserialize(base64_decode($string)); } } /** * Compose a SQL WHERE clause for the cache. * * Note: We need to use the cache during bootstrap, so we don't have * full access to DAO services. * * @param string $group * @param string|null $path * Filter by path. If NULL, then return any paths. * @param int|null $componentID * Filter by component. If NULL, then look for explicitly NULL records. * @return string */ protected static function whereCache($group, $path, $componentID) { $clauses = []; $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($group) . '"'); if ($path) { $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"'); } if ($componentID && is_numeric($componentID)) { $clauses[] = ('component_id = ' . (int) $componentID); } return $clauses ? implode(' AND ', $clauses) : '(1)'; } /** * Normalize a cache key. * * This bridges an impedance mismatch between our traditional caching * and PSR-16 -- PSR-16 accepts a narrower range of cache keys. * * @param string $key * Ex: 'ab/cd:ef' * @return string * Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'. * A similar key, but suitable for use with PSR-16-compliant cache providers. * @deprecated * @see CRM_Utils_Cache::cleanKey() */ public static function cleanKey($key) { return CRM_Utils_Cache::cleanKey($key); } }