3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * BAO object for civicrm_cache table.
31 * This is a database cache and is persisted across sessions. Typically we use
32 * this to store meta data (like profile fields, custom fields etc).
34 * The group_name column is used for grouping together all cache elements that logically belong to the same set.
35 * Thus all session cache entries are grouped under 'CiviCRM Session'. This allows us to delete all entries of
36 * a specific group if needed.
38 * The path column allows us to differentiate between items in that group. Thus for the session cache, the path is
39 * the unique form name for each form (per user)
41 class CRM_Core_BAO_Cache
extends CRM_Core_DAO_Cache
{
44 * @var array ($cacheKey => $cacheValue)
46 static $_cache = NULL;
49 * Retrieve an item from the DB cache.
51 * @param string $group
52 * (required) The group name of the item.
54 * (required) The path under which this item is stored.
55 * @param int $componentID
56 * The optional component ID (so componenets can share the same name space).
59 * The data if present in cache, else null
61 public static function &getItem($group, $path, $componentID = NULL) {
62 if (self
::$_cache === NULL) {
63 self
::$_cache = array();
66 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
67 if (!array_key_exists($argString, self
::$_cache)) {
68 $cache = CRM_Utils_Cache
::singleton();
69 self
::$_cache[$argString] = $cache->get($argString);
70 if (!self
::$_cache[$argString]) {
71 $table = self
::getTableName();
72 $where = self
::whereCache($group, $path, $componentID);
73 $rawData = CRM_Core_DAO
::singleValueQuery("SELECT data FROM $table WHERE $where");
74 $data = $rawData ?
unserialize($rawData) : NULL;
76 self
::$_cache[$argString] = $data;
77 $cache->set($argString, self
::$_cache[$argString]);
80 return self
::$_cache[$argString];
84 * Retrieve all items in a group.
86 * @param string $group
87 * (required) The group name of the item.
88 * @param int $componentID
89 * The optional component ID (so componenets can share the same name space).
92 * The data if present in cache, else null
94 public static function &getItems($group, $componentID = NULL) {
95 if (self
::$_cache === NULL) {
96 self
::$_cache = array();
99 $argString = "CRM_CT_CI_{$group}_{$componentID}";
100 if (!array_key_exists($argString, self
::$_cache)) {
101 $cache = CRM_Utils_Cache
::singleton();
102 self
::$_cache[$argString] = $cache->get($argString);
103 if (!self
::$_cache[$argString]) {
104 $table = self
::getTableName();
105 $where = self
::whereCache($group, NULL, $componentID);
106 $dao = CRM_Core_DAO
::executeQuery("SELECT path, data FROM $table WHERE $where");
109 while ($dao->fetch()) {
110 $result[$dao->path
] = unserialize($dao->data
);
114 self
::$_cache[$argString] = $result;
115 $cache->set($argString, self
::$_cache[$argString]);
119 return self
::$_cache[$argString];
123 * Store an item in the DB cache.
125 * @param object $data
126 * (required) A reference to the data that will be serialized and stored.
127 * @param string $group
128 * (required) The group name of the item.
129 * @param string $path
130 * (required) The path under which this item is stored.
131 * @param int $componentID
132 * The optional component ID (so componenets can share the same name space).
134 public static function setItem(&$data, $group, $path, $componentID = NULL) {
135 if (self
::$_cache === NULL) {
136 self
::$_cache = array();
139 // get a lock so that multiple ajax requests on the same page
140 // dont trample on each other
142 $lock = Civi
::lockManager()->acquire("cache.{$group}_{$path}._{$componentID}");
143 if (!$lock->isAcquired()) {
144 CRM_Core_Error
::fatal();
147 $table = self
::getTableName();
148 $where = self
::whereCache($group, $path, $componentID);
149 $dataExists = CRM_Core_DAO
::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}");
150 $now = date('Y-m-d H:i:s'); // FIXME - Use SQL NOW() or CRM_Utils_Time?
151 $dataSerialized = serialize($data);
153 // This table has a wonky index, so we cannot use REPLACE or
154 // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE).
156 $sql = "UPDATE $table SET data = %1, created_date = %2 WHERE {$where}";
158 1 => array($dataSerialized, 'String'),
159 2 => array($now, 'String'),
161 $dao = CRM_Core_DAO
::executeQuery($sql, $args, TRUE, NULL, FALSE, FALSE);
164 $insert = CRM_Utils_SQL_Insert
::into($table)
166 'group_name' => $group,
168 'component_id' => $componentID,
169 'data' => $dataSerialized,
170 'created_date' => $now,
172 $dao = CRM_Core_DAO
::executeQuery($insert->toSQL(), array(), TRUE, NULL, FALSE, FALSE);
179 // cache coherency - refresh or remove dependent caches
181 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
182 $cache = CRM_Utils_Cache
::singleton();
183 $data = unserialize($dataSerialized);
184 self
::$_cache[$argString] = $data;
185 $cache->set($argString, $data);
187 $argString = "CRM_CT_CI_{$group}_{$componentID}";
188 unset(self
::$_cache[$argString]);
189 $cache->delete($argString);
193 * Delete all the cache elements that belong to a group OR delete the entire cache if group is not specified.
195 * @param string $group
196 * The group name of the entries to be deleted.
197 * @param string $path
198 * Path of the item that needs to be deleted.
199 * @param bool $clearAll clear all caches
201 public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) {
202 $table = self
::getTableName();
203 $where = self
::whereCache($group, $path, NULL);
204 CRM_Core_DAO
::executeQuery("DELETE FROM $table WHERE $where");
207 // also reset ACL Cache
208 CRM_ACL_BAO_Cache
::resetCache();
210 // also reset memory cache if any
211 CRM_Utils_System
::flushCache();
216 * The next two functions are internal functions used to store and retrieve session from
217 * the database cache. This keeps the session to a limited size and allows us to
218 * create separate session scopes for each form in a tab
222 * This function takes entries from the session array and stores it in the cache.
224 * It also deletes the entries from the $_SESSION object (for a smaller session size)
226 * @param array $names
227 * Array of session values that should be persisted.
228 * This is either a form name + qfKey or just a form name
229 * (in the case of profile)
230 * @param bool $resetSession
231 * Should session state be reset on completion of DB store?.
233 public static function storeSessionToCache($names, $resetSession = TRUE) {
234 foreach ($names as $key => $sessionName) {
235 if (is_array($sessionName)) {
237 if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
238 $value = $_SESSION[$sessionName[0]][$sessionName[1]];
240 self
::setItem($value, 'CiviCRM Session', "{$sessionName[0]}_{$sessionName[1]}");
242 $_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
243 unset($_SESSION[$sessionName[0]][$sessionName[1]]);
248 if (!empty($_SESSION[$sessionName])) {
249 $value = $_SESSION[$sessionName];
251 self
::setItem($value, 'CiviCRM Session', $sessionName);
253 $_SESSION[$sessionName] = NULL;
254 unset($_SESSION[$sessionName]);
262 /* Retrieve the session values from the cache and populate the $_SESSION array
264 * @param array $names
265 * Array of session values that should be persisted.
266 * This is either a form name + qfKey or just a form name
267 * (in the case of profile)
271 * Restore session from cache.
273 * @param string $names
275 public static function restoreSessionFromCache($names) {
276 foreach ($names as $key => $sessionName) {
277 if (is_array($sessionName)) {
278 $value = self
::getItem('CiviCRM Session',
279 "{$sessionName[0]}_{$sessionName[1]}"
282 $_SESSION[$sessionName[0]][$sessionName[1]] = $value;
286 $value = self
::getItem('CiviCRM Session',
290 $_SESSION[$sessionName] = $value;
297 * Do periodic cleanup of the CiviCRM session table.
299 * Also delete all session cache entries which are a couple of days old.
300 * This keeps the session cache to a manageable size
301 * Delete Contribution page session caches more energetically.
303 * @param bool $session
305 * @param bool $prevNext
307 public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE) {
308 // first delete all sessions more than 20 minutes old which are related to any potential transaction
309 $timeIntervalMins = (int) Civi
::settings()->get('secure_cache_timeout_minutes');
310 if ($timeIntervalMins && $session) {
311 $transactionPages = array(
312 'CRM_Contribute_Controller_Contribution',
313 'CRM_Event_Controller_Registration',
318 date('Y-m-d H:i:s', time() - $timeIntervalMins * 60),
322 foreach ($transactionPages as $trPage) {
323 $params[] = array("%${trPage}%", 'String');
324 $where[] = 'path LIKE %' . count($params);
328 DELETE FROM civicrm_cache
329 WHERE group_name = 'CiviCRM Session'
330 AND created_date <= %1
331 AND (" . implode(' OR ', $where) . ")";
332 CRM_Core_DAO
::executeQuery($sql, $params);
334 // clean up the session cache every $cacheCleanUpNumber probabilistically
335 $cleanUpNumber = 757;
337 // clean up all sessions older than $cacheTimeIntervalDays days
338 $timeIntervalDays = 2;
340 if (mt_rand(1, 100000) %
$cleanUpNumber == 0) {
341 $session = $table = $prevNext = TRUE;
344 if (!$session && !$table && !$prevNext) {
349 // delete all PrevNext caches
350 CRM_Core_BAO_PrevNextCache
::cleanupCache();
354 CRM_Core_Config
::clearTempTables($timeIntervalDays . ' day');
360 DELETE FROM civicrm_cache
361 WHERE group_name = 'CiviCRM Session'
362 AND created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
364 CRM_Core_DAO
::executeQuery($sql);
369 * Compose a SQL WHERE clause for the cache.
371 * Note: We need to use the cache during bootstrap, so we don't have
372 * full access to DAO services.
374 * @param string $group
375 * @param string|NULL $path
376 * Filter by path. If NULL, then return any paths.
377 * @param int|NULL $componentID
378 * Filter by component. If NULL, then look for explicitly NULL records.
381 protected static function whereCache($group, $path, $componentID) {
383 $clauses[] = ('group_name = "' . CRM_Core_DAO
::escapeString($group) . '"');
385 $clauses[] = ('path = "' . CRM_Core_DAO
::escapeString($path) . '"');
387 if ($componentID && is_numeric($componentID)) {
388 $clauses[] = ('component_id = ' . (int) $componentID);
390 return $clauses ?
implode(' AND ', $clauses) : '(1)';