Merge pull request #22992 from eileenmcnaughton/billingnot
[civicrm-core.git] / CRM / Core / BAO / Cache.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035 11
6a488035 12/**
192d36c5 13 * BAO object for civicrm_cache table.
14 *
15 * This is a database cache and is persisted across sessions. Typically we use
6a488035
TO
16 * this to store meta data (like profile fields, custom fields etc).
17 *
18 * The group_name column is used for grouping together all cache elements that logically belong to the same set.
19 * Thus all session cache entries are grouped under 'CiviCRM Session'. This allows us to delete all entries of
20 * a specific group if needed.
21 *
22 * The path column allows us to differentiate between items in that group. Thus for the session cache, the path is
23 * the unique form name for each form (per user)
24 */
25class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
26
19707a63
TO
27 /**
28 * When store session/form state, how long should the data be retained?
29 *
041ecc95 30 * Default is Two days: 2*24*60*60
31 *
19707a63
TO
32 * @var int, number of second
33 */
cf9ccf98 34 const DEFAULT_SESSION_TTL = 172800;
19707a63 35
33660480
SL
36 /**
37 * Cleanup ACL and System Level caches
38 */
39 public static function resetCaches() {
33660480
SL
40 CRM_Utils_System::flushCache();
41 }
42
6a488035
TO
43 /**
44 * The next two functions are internal functions used to store and retrieve session from
45 * the database cache. This keeps the session to a limited size and allows us to
46 * create separate session scopes for each form in a tab
6a488035
TO
47 */
48
49 /**
50 * This function takes entries from the session array and stores it in the cache.
1a4e6781 51 *
6a488035
TO
52 * It also deletes the entries from the $_SESSION object (for a smaller session size)
53 *
6a0b768e
TO
54 * @param array $names
55 * Array of session values that should be persisted.
6a488035
TO
56 * This is either a form name + qfKey or just a form name
57 * (in the case of profile)
6a0b768e
TO
58 * @param bool $resetSession
59 * Should session state be reset on completion of DB store?.
6a488035 60 */
00be9182 61 public static function storeSessionToCache($names, $resetSession = TRUE) {
6a488035
TO
62 foreach ($names as $key => $sessionName) {
63 if (is_array($sessionName)) {
2aa397bc 64 $value = NULL;
6a488035
TO
65 if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
66 $value = $_SESSION[$sessionName[0]][$sessionName[1]];
67 }
19707a63
TO
68 $key = "{$sessionName[0]}_{$sessionName[1]}";
69 Civi::cache('session')->set($key, $value, self::pickSessionTtl($key));
2aa397bc
TO
70 if ($resetSession) {
71 $_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
72 unset($_SESSION[$sessionName[0]][$sessionName[1]]);
6a488035 73 }
2aa397bc 74 }
6a488035 75 else {
2aa397bc 76 $value = NULL;
6a488035
TO
77 if (!empty($_SESSION[$sessionName])) {
78 $value = $_SESSION[$sessionName];
79 }
19707a63 80 Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName));
2aa397bc
TO
81 if ($resetSession) {
82 $_SESSION[$sessionName] = NULL;
83 unset($_SESSION[$sessionName]);
6a488035
TO
84 }
85 }
2aa397bc 86 }
6a488035
TO
87
88 self::cleanup();
89 }
90
91 /* Retrieve the session values from the cache and populate the $_SESSION array
006389de
TO
92 *
93 * @param array $names
94 * Array of session values that should be persisted.
95 * This is either a form name + qfKey or just a form name
96 * (in the case of profile)
006389de 97 */
6a488035 98
b5c2afd0 99 /**
1a4e6781
EM
100 * Restore session from cache.
101 *
100fef9d 102 * @param string $names
b5c2afd0 103 */
00be9182 104 public static function restoreSessionFromCache($names) {
6a488035
TO
105 foreach ($names as $key => $sessionName) {
106 if (is_array($sessionName)) {
19707a63 107 $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}");
6a488035
TO
108 if ($value) {
109 $_SESSION[$sessionName[0]][$sessionName[1]] = $value;
110 }
111 }
112 else {
19707a63 113 $value = Civi::cache('session')->get($sessionName);
6a488035
TO
114 if ($value) {
115 $_SESSION[$sessionName] = $value;
116 }
117 }
118 }
119 }
120
19707a63
TO
121 /**
122 * Determine how long session-state should be retained.
123 *
124 * @param string $sessionKey
125 * Ex: '_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654_container'
126 * Ex: 'CiviCRM_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654'
127 * @return int
128 * Number of seconds.
129 */
130 protected static function pickSessionTtl($sessionKey) {
131 $secureSessionTimeoutMinutes = (int) Civi::settings()->get('secure_cache_timeout_minutes');
132 if ($secureSessionTimeoutMinutes) {
be2fb01f 133 $transactionPages = [
19707a63
TO
134 'CRM_Contribute_Controller_Contribution',
135 'CRM_Event_Controller_Registration',
be2fb01f 136 ];
19707a63
TO
137 foreach ($transactionPages as $transactionPage) {
138 if (strpos($sessionKey, $transactionPage) !== FALSE) {
139 return $secureSessionTimeoutMinutes * 60;
140 }
141 }
142 }
143
144 return self::DEFAULT_SESSION_TTL;
145 }
146
6a488035 147 /**
1a4e6781
EM
148 * Do periodic cleanup of the CiviCRM session table.
149 *
150 * Also delete all session cache entries which are a couple of days old.
151 * This keeps the session cache to a manageable size
f26d31eb 152 * Delete Contribution page session caches more energetically.
6a488035 153 *
554259a7
EM
154 * @param bool $session
155 * @param bool $table
156 * @param bool $prevNext
518fa0ee 157 * @param bool $expired
6a488035 158 */
fd33fead 159 public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) {
6a488035
TO
160 // clean up the session cache every $cacheCleanUpNumber probabilistically
161 $cleanUpNumber = 757;
162
163 // clean up all sessions older than $cacheTimeIntervalDays days
164 $timeIntervalDays = 2;
6a488035
TO
165
166 if (mt_rand(1, 100000) % $cleanUpNumber == 0) {
fd33fead 167 $expired = $session = $table = $prevNext = TRUE;
6a488035
TO
168 }
169
fd33fead 170 if (!$session && !$table && !$prevNext && !$expired) {
6a488035
TO
171 return;
172 }
173
f9f40af3 174 if ($prevNext) {
6a488035 175 // delete all PrevNext caches
b3e4463a 176 Civi::service('prevnext')->cleanup();
6a488035
TO
177 }
178
f9f40af3 179 if ($table) {
40c712ed 180 CRM_Core_Config::clearTempTables($timeIntervalDays . ' day');
6a488035
TO
181 }
182
f9f40af3 183 if ($session) {
19707a63
TO
184 // Session caches are just regular caches, so they expire naturally per TTL.
185 $expired = TRUE;
8c52547a 186 }
fd33fead
TO
187
188 if ($expired) {
189 $sql = "DELETE FROM civicrm_cache WHERE expired_date < %1";
190 $params = [
191 1 => [date(CRM_Utils_Cache_SqlGroup::TS_FMT, CRM_Utils_Time::getTimeRaw()), 'String'],
192 ];
193 CRM_Core_DAO::executeQuery($sql, $params);
194 }
6a488035 195 }
96025800 196
3c643fe7
TO
197 /**
198 * (Quasi-private) Encode an object/array/string/int as a string.
199 *
200 * @param $mixed
201 * @return string
202 */
203 public static function encode($mixed) {
204 return base64_encode(serialize($mixed));
205 }
206
207 /**
208 * (Quasi-private) Decode an object/array/string/int from a string.
209 *
210 * @param $string
211 * @return mixed
212 */
213 public static function decode($string) {
214 // Upgrade support -- old records (serialize) always have this punctuation,
215 // and new records (base64) never do.
216 if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) {
217 return unserialize($string);
218 }
219 else {
220 return unserialize(base64_decode($string));
221 }
222 }
223
61d29839
TO
224 /**
225 * Compose a SQL WHERE clause for the cache.
226 *
227 * Note: We need to use the cache during bootstrap, so we don't have
228 * full access to DAO services.
229 *
230 * @param string $group
e97c66ff 231 * @param string|null $path
61d29839 232 * Filter by path. If NULL, then return any paths.
e97c66ff 233 * @param int|null $componentID
61d29839
TO
234 * Filter by component. If NULL, then look for explicitly NULL records.
235 * @return string
236 */
237 protected static function whereCache($group, $path, $componentID) {
be2fb01f 238 $clauses = [];
61d29839
TO
239 $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($group) . '"');
240 if ($path) {
241 $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"');
242 }
243 if ($componentID && is_numeric($componentID)) {
244 $clauses[] = ('component_id = ' . (int) $componentID);
245 }
246 return $clauses ? implode(' AND ', $clauses) : '(1)';
247 }
248
bd76ee83
TO
249 /**
250 * Normalize a cache key.
251 *
252 * This bridges an impedance mismatch between our traditional caching
253 * and PSR-16 -- PSR-16 accepts a narrower range of cache keys.
254 *
255 * @param string $key
256 * Ex: 'ab/cd:ef'
257 * @return string
258 * Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
259 * A similar key, but suitable for use with PSR-16-compliant cache providers.
77f080cb
TO
260 * @deprecated
261 * @see CRM_Utils_Cache::cleanKey()
bd76ee83
TO
262 */
263 public static function cleanKey($key) {
90936fd3 264 CRM_Core_Error::deprecatedFunctionWarning('CRM_Utils_Cache::cleanKey');
77f080cb 265 return CRM_Utils_Cache::cleanKey($key);
bd76ee83
TO
266 }
267
6a488035 268}