Add in Deprecation warnings on Cache functons
[civicrm-core.git] / CRM / Core / BAO / Cache.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035 27
6a488035 28/**
192d36c5 29 * BAO object for civicrm_cache table.
30 *
31 * This is a database cache and is persisted across sessions. Typically we use
6a488035
TO
32 * this to store meta data (like profile fields, custom fields etc).
33 *
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.
37 *
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)
40 */
41class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
42
19707a63
TO
43 /**
44 * When store session/form state, how long should the data be retained?
45 *
041ecc95 46 * Default is Two days: 2*24*60*60
47 *
19707a63
TO
48 * @var int, number of second
49 */
cf9ccf98 50 const DEFAULT_SESSION_TTL = 172800;
19707a63 51
e79e9c87 52 /**
041ecc95 53 * Cache.
54 *
55 * Format is ($cacheKey => $cacheValue)
56 *
57 * @var array
e79e9c87 58 */
518fa0ee 59 public static $_cache = NULL;
e79e9c87 60
6a488035 61 /**
fe482240 62 * Retrieve an item from the DB cache.
6a488035 63 *
6a0b768e
TO
64 * @param string $group
65 * (required) The group name of the item.
66 * @param string $path
67 * (required) The path under which this item is stored.
68 * @param int $componentID
69 * The optional component ID (so componenets can share the same name space).
6a488035 70 *
a6c01b45
CW
71 * @return object
72 * The data if present in cache, else null
920fa38f 73 * @deprecated
6a488035 74 */
00be9182 75 public static function &getItem($group, $path, $componentID = NULL) {
cf4c32dd
SL
76 CRM_Core_Error::deprecatedFunctionWarning(
77 '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'
78 );
88dfeb5c 79 if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) {
b5e7d576
TO
80 $value = $adapter::getItem($group, $path, $componentID);
81 return $value;
5a302bbc
TO
82 }
83
e79e9c87 84 if (self::$_cache === NULL) {
be2fb01f 85 self::$_cache = [];
def0681b 86 }
6a488035 87
def0681b 88 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
e79e9c87 89 if (!array_key_exists($argString, self::$_cache)) {
def0681b 90 $cache = CRM_Utils_Cache::singleton();
972b0e25
CB
91 $cleanKey = self::cleanKey($argString);
92 self::$_cache[$argString] = $cache->get($cleanKey);
22263d41 93 if (self::$_cache[$argString] === NULL) {
61d29839
TO
94 $table = self::getTableName();
95 $where = self::whereCache($group, $path, $componentID);
5d1e8768 96 $rawData = CRM_Core_DAO::singleValueQuery("SELECT data FROM $table WHERE $where");
3c643fe7 97 $data = $rawData ? self::decode($rawData) : NULL;
61d29839 98
e79e9c87 99 self::$_cache[$argString] = $data;
ce18ad0f 100 if ($data !== NULL) {
101 // Do not cache 'null' as that is most likely a cache miss & we shouldn't then cache it.
102 $cache->set($cleanKey, self::$_cache[$argString]);
103 }
def0681b 104 }
6a488035 105 }
e79e9c87 106 return self::$_cache[$argString];
6a488035
TO
107 }
108
109 /**
fe482240 110 * Retrieve all items in a group.
6a488035 111 *
6a0b768e
TO
112 * @param string $group
113 * (required) The group name of the item.
114 * @param int $componentID
115 * The optional component ID (so componenets can share the same name space).
6a488035 116 *
a6c01b45
CW
117 * @return object
118 * The data if present in cache, else null
920fa38f 119 * @deprecated
6a488035 120 */
00be9182 121 public static function &getItems($group, $componentID = NULL) {
cf4c32dd
SL
122 CRM_Core_Error::deprecatedFunctionWarning(
123 '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'
124 );
88dfeb5c 125 if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) {
5a302bbc
TO
126 return $adapter::getItems($group, $componentID);
127 }
128
e79e9c87 129 if (self::$_cache === NULL) {
be2fb01f 130 self::$_cache = [];
def0681b 131 }
6a488035 132
def0681b 133 $argString = "CRM_CT_CI_{$group}_{$componentID}";
e79e9c87 134 if (!array_key_exists($argString, self::$_cache)) {
def0681b 135 $cache = CRM_Utils_Cache::singleton();
972b0e25
CB
136 $cleanKey = self::cleanKey($argString);
137 self::$_cache[$argString] = $cache->get($cleanKey);
e79e9c87 138 if (!self::$_cache[$argString]) {
61d29839
TO
139 $table = self::getTableName();
140 $where = self::whereCache($group, NULL, $componentID);
141 $dao = CRM_Core_DAO::executeQuery("SELECT path, data FROM $table WHERE $where");
6a488035 142
be2fb01f 143 $result = [];
def0681b 144 while ($dao->fetch()) {
3c643fe7 145 $result[$dao->path] = self::decode($dao->data);
def0681b 146 }
def0681b 147
e79e9c87 148 self::$_cache[$argString] = $result;
972b0e25 149 $cache->set($cleanKey, self::$_cache[$argString]);
def0681b 150 }
6a488035 151 }
def0681b 152
e79e9c87 153 return self::$_cache[$argString];
6a488035
TO
154 }
155
156 /**
fe482240 157 * Store an item in the DB cache.
6a488035 158 *
6a0b768e
TO
159 * @param object $data
160 * (required) A reference to the data that will be serialized and stored.
161 * @param string $group
162 * (required) The group name of the item.
163 * @param string $path
164 * (required) The path under which this item is stored.
165 * @param int $componentID
166 * The optional component ID (so componenets can share the same name space).
920fa38f 167 * @deprecated
6a488035 168 */
00be9182 169 public static function setItem(&$data, $group, $path, $componentID = NULL) {
cf4c32dd
SL
170 CRM_Core_Error::deprecatedFunctionWarning(
171 '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'
172 );
88dfeb5c 173 if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) {
5a302bbc
TO
174 return $adapter::setItem($data, $group, $path, $componentID);
175 }
176
e79e9c87 177 if (self::$_cache === NULL) {
be2fb01f 178 self::$_cache = [];
def0681b
KJ
179 }
180
6a488035
TO
181 // get a lock so that multiple ajax requests on the same page
182 // dont trample on each other
183 // CRM-11234
83617886 184 $lock = Civi::lockManager()->acquire("cache.{$group}_{$path}._{$componentID}");
6a488035
TO
185 if (!$lock->isAcquired()) {
186 CRM_Core_Error::fatal();
187 }
188
61d29839
TO
189 $table = self::getTableName();
190 $where = self::whereCache($group, $path, $componentID);
21ca2cb6 191 $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}");
cf9ccf98
TO
192 // FIXME - Use SQL NOW() or CRM_Utils_Time?
193 $now = date('Y-m-d H:i:s');
3c643fe7 194 $dataSerialized = self::encode($data);
61d29839
TO
195
196 // This table has a wonky index, so we cannot use REPLACE or
197 // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE).
21ca2cb6 198 if ($dataExists) {
199 $sql = "UPDATE $table SET data = %1, created_date = %2 WHERE {$where}";
be2fb01f
CW
200 $args = [
201 1 => [$dataSerialized, 'String'],
202 2 => [$now, 'String'],
203 ];
3d6878c6 204 $dao = CRM_Core_DAO::executeQuery($sql, $args, TRUE, NULL, FALSE, FALSE);
61d29839
TO
205 }
206 else {
207 $insert = CRM_Utils_SQL_Insert::into($table)
be2fb01f 208 ->row([
61d29839
TO
209 'group_name' => $group,
210 'path' => $path,
211 'component_id' => $componentID,
212 'data' => $dataSerialized,
213 'created_date' => $now,
be2fb01f
CW
214 ]);
215 $dao = CRM_Core_DAO::executeQuery($insert->toSQL(), [], TRUE, NULL, FALSE, FALSE);
61d29839 216 }
6a488035
TO
217
218 $lock->release();
219
80259ba2
TO
220 // cache coherency - refresh or remove dependent caches
221
def0681b
KJ
222 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
223 $cache = CRM_Utils_Cache::singleton();
3c643fe7 224 $data = self::decode($dataSerialized);
e79e9c87 225 self::$_cache[$argString] = $data;
bd76ee83 226 $cache->set(self::cleanKey($argString), $data);
80259ba2
TO
227
228 $argString = "CRM_CT_CI_{$group}_{$componentID}";
e79e9c87 229 unset(self::$_cache[$argString]);
bd76ee83 230 $cache->delete(self::cleanKey($argString));
6a488035
TO
231 }
232
233 /**
1a4e6781 234 * Delete all the cache elements that belong to a group OR delete the entire cache if group is not specified.
6a488035 235 *
6a0b768e
TO
236 * @param string $group
237 * The group name of the entries to be deleted.
238 * @param string $path
239 * Path of the item that needs to be deleted.
1a4e6781 240 * @param bool $clearAll clear all caches
920fa38f 241 * @deprecated
6a488035 242 */
00be9182 243 public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) {
cf4c32dd
SL
244 CRM_Core_Error::deprecatedFunctionWarning(
245 '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'
246 );
88dfeb5c 247 if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) {
5a302bbc
TO
248 return $adapter::deleteGroup($group, $path);
249 }
250 else {
251 $table = self::getTableName();
252 $where = self::whereCache($group, $path, NULL);
253 CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where");
254 }
6a488035
TO
255
256 if ($clearAll) {
33660480 257 self::resetCaches();
6a488035
TO
258 }
259 }
260
33660480
SL
261 /**
262 * Cleanup ACL and System Level caches
263 */
264 public static function resetCaches() {
265 // also reset ACL Cache
266 // @todo why is this called when CRM_Utils_System::flushCache() does it as well.
267 CRM_ACL_BAO_Cache::resetCache();
268
269 // also reset memory cache if any
270 CRM_Utils_System::flushCache();
271 }
272
6a488035
TO
273 /**
274 * The next two functions are internal functions used to store and retrieve session from
275 * the database cache. This keeps the session to a limited size and allows us to
276 * create separate session scopes for each form in a tab
6a488035
TO
277 */
278
279 /**
280 * This function takes entries from the session array and stores it in the cache.
1a4e6781 281 *
6a488035
TO
282 * It also deletes the entries from the $_SESSION object (for a smaller session size)
283 *
6a0b768e
TO
284 * @param array $names
285 * Array of session values that should be persisted.
6a488035
TO
286 * This is either a form name + qfKey or just a form name
287 * (in the case of profile)
6a0b768e
TO
288 * @param bool $resetSession
289 * Should session state be reset on completion of DB store?.
6a488035 290 */
00be9182 291 public static function storeSessionToCache($names, $resetSession = TRUE) {
6a488035
TO
292 foreach ($names as $key => $sessionName) {
293 if (is_array($sessionName)) {
2aa397bc 294 $value = NULL;
6a488035
TO
295 if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
296 $value = $_SESSION[$sessionName[0]][$sessionName[1]];
297 }
19707a63
TO
298 $key = "{$sessionName[0]}_{$sessionName[1]}";
299 Civi::cache('session')->set($key, $value, self::pickSessionTtl($key));
2aa397bc
TO
300 if ($resetSession) {
301 $_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
302 unset($_SESSION[$sessionName[0]][$sessionName[1]]);
6a488035 303 }
2aa397bc 304 }
6a488035 305 else {
2aa397bc 306 $value = NULL;
6a488035
TO
307 if (!empty($_SESSION[$sessionName])) {
308 $value = $_SESSION[$sessionName];
309 }
19707a63 310 Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName));
2aa397bc
TO
311 if ($resetSession) {
312 $_SESSION[$sessionName] = NULL;
313 unset($_SESSION[$sessionName]);
6a488035
TO
314 }
315 }
2aa397bc 316 }
6a488035
TO
317
318 self::cleanup();
319 }
320
321 /* Retrieve the session values from the cache and populate the $_SESSION array
006389de
TO
322 *
323 * @param array $names
324 * Array of session values that should be persisted.
325 * This is either a form name + qfKey or just a form name
326 * (in the case of profile)
006389de 327 */
6a488035 328
b5c2afd0 329 /**
1a4e6781
EM
330 * Restore session from cache.
331 *
100fef9d 332 * @param string $names
b5c2afd0 333 */
00be9182 334 public static function restoreSessionFromCache($names) {
6a488035
TO
335 foreach ($names as $key => $sessionName) {
336 if (is_array($sessionName)) {
19707a63 337 $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}");
6a488035
TO
338 if ($value) {
339 $_SESSION[$sessionName[0]][$sessionName[1]] = $value;
340 }
341 }
342 else {
19707a63 343 $value = Civi::cache('session')->get($sessionName);
6a488035
TO
344 if ($value) {
345 $_SESSION[$sessionName] = $value;
346 }
347 }
348 }
349 }
350
19707a63
TO
351 /**
352 * Determine how long session-state should be retained.
353 *
354 * @param string $sessionKey
355 * Ex: '_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654_container'
356 * Ex: 'CiviCRM_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654'
357 * @return int
358 * Number of seconds.
359 */
360 protected static function pickSessionTtl($sessionKey) {
361 $secureSessionTimeoutMinutes = (int) Civi::settings()->get('secure_cache_timeout_minutes');
362 if ($secureSessionTimeoutMinutes) {
be2fb01f 363 $transactionPages = [
19707a63
TO
364 'CRM_Contribute_Controller_Contribution',
365 'CRM_Event_Controller_Registration',
be2fb01f 366 ];
19707a63
TO
367 foreach ($transactionPages as $transactionPage) {
368 if (strpos($sessionKey, $transactionPage) !== FALSE) {
369 return $secureSessionTimeoutMinutes * 60;
370 }
371 }
372 }
373
374 return self::DEFAULT_SESSION_TTL;
375 }
376
6a488035 377 /**
1a4e6781
EM
378 * Do periodic cleanup of the CiviCRM session table.
379 *
380 * Also delete all session cache entries which are a couple of days old.
381 * This keeps the session cache to a manageable size
f26d31eb 382 * Delete Contribution page session caches more energetically.
6a488035 383 *
554259a7
EM
384 * @param bool $session
385 * @param bool $table
386 * @param bool $prevNext
518fa0ee 387 * @param bool $expired
6a488035 388 */
fd33fead 389 public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) {
6a488035
TO
390 // clean up the session cache every $cacheCleanUpNumber probabilistically
391 $cleanUpNumber = 757;
392
393 // clean up all sessions older than $cacheTimeIntervalDays days
394 $timeIntervalDays = 2;
6a488035
TO
395
396 if (mt_rand(1, 100000) % $cleanUpNumber == 0) {
fd33fead 397 $expired = $session = $table = $prevNext = TRUE;
6a488035
TO
398 }
399
fd33fead 400 if (!$session && !$table && !$prevNext && !$expired) {
6a488035
TO
401 return;
402 }
403
f9f40af3 404 if ($prevNext) {
6a488035
TO
405 // delete all PrevNext caches
406 CRM_Core_BAO_PrevNextCache::cleanupCache();
407 }
408
f9f40af3 409 if ($table) {
40c712ed 410 CRM_Core_Config::clearTempTables($timeIntervalDays . ' day');
6a488035
TO
411 }
412
f9f40af3 413 if ($session) {
19707a63
TO
414 // Session caches are just regular caches, so they expire naturally per TTL.
415 $expired = TRUE;
8c52547a 416 }
fd33fead
TO
417
418 if ($expired) {
419 $sql = "DELETE FROM civicrm_cache WHERE expired_date < %1";
420 $params = [
421 1 => [date(CRM_Utils_Cache_SqlGroup::TS_FMT, CRM_Utils_Time::getTimeRaw()), 'String'],
422 ];
423 CRM_Core_DAO::executeQuery($sql, $params);
424 }
6a488035 425 }
96025800 426
3c643fe7
TO
427 /**
428 * (Quasi-private) Encode an object/array/string/int as a string.
429 *
430 * @param $mixed
431 * @return string
432 */
433 public static function encode($mixed) {
434 return base64_encode(serialize($mixed));
435 }
436
437 /**
438 * (Quasi-private) Decode an object/array/string/int from a string.
439 *
440 * @param $string
441 * @return mixed
442 */
443 public static function decode($string) {
444 // Upgrade support -- old records (serialize) always have this punctuation,
445 // and new records (base64) never do.
446 if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) {
447 return unserialize($string);
448 }
449 else {
450 return unserialize(base64_decode($string));
451 }
452 }
453
61d29839
TO
454 /**
455 * Compose a SQL WHERE clause for the cache.
456 *
457 * Note: We need to use the cache during bootstrap, so we don't have
458 * full access to DAO services.
459 *
460 * @param string $group
e97c66ff 461 * @param string|null $path
61d29839 462 * Filter by path. If NULL, then return any paths.
e97c66ff 463 * @param int|null $componentID
61d29839
TO
464 * Filter by component. If NULL, then look for explicitly NULL records.
465 * @return string
466 */
467 protected static function whereCache($group, $path, $componentID) {
be2fb01f 468 $clauses = [];
61d29839
TO
469 $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($group) . '"');
470 if ($path) {
471 $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"');
472 }
473 if ($componentID && is_numeric($componentID)) {
474 $clauses[] = ('component_id = ' . (int) $componentID);
475 }
476 return $clauses ? implode(' AND ', $clauses) : '(1)';
477 }
478
bd76ee83
TO
479 /**
480 * Normalize a cache key.
481 *
482 * This bridges an impedance mismatch between our traditional caching
483 * and PSR-16 -- PSR-16 accepts a narrower range of cache keys.
484 *
485 * @param string $key
486 * Ex: 'ab/cd:ef'
487 * @return string
488 * Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
489 * A similar key, but suitable for use with PSR-16-compliant cache providers.
77f080cb
TO
490 * @deprecated
491 * @see CRM_Utils_Cache::cleanKey()
bd76ee83
TO
492 */
493 public static function cleanKey($key) {
77f080cb 494 return CRM_Utils_Cache::cleanKey($key);
bd76ee83
TO
495 }
496
6a488035 497}