INFRA-132 - CRM/Contribute - Misc
[civicrm-core.git] / CRM / Core / BAO / Cache.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35
36/**
37 * BAO object for civicrm_cache table. This is a database cache and is persisted across sessions. Typically we use
38 * this to store meta data (like profile fields, custom fields etc).
39 *
40 * The group_name column is used for grouping together all cache elements that logically belong to the same set.
41 * Thus all session cache entries are grouped under 'CiviCRM Session'. This allows us to delete all entries of
42 * a specific group if needed.
43 *
44 * The path column allows us to differentiate between items in that group. Thus for the session cache, the path is
45 * the unique form name for each form (per user)
46 */
47class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
48
e79e9c87
TO
49 /**
50 * @var array ($cacheKey => $cacheValue)
51 */
52 static $_cache = NULL;
53
6a488035
TO
54 /**
55 * Retrieve an item from the DB cache
56 *
6a0b768e
TO
57 * @param string $group
58 * (required) The group name of the item.
59 * @param string $path
60 * (required) The path under which this item is stored.
61 * @param int $componentID
62 * The optional component ID (so componenets can share the same name space).
6a488035
TO
63 *
64 * @return object The data if present in cache, else null
65 * @static
6a488035 66 */
00be9182 67 public static function &getItem($group, $path, $componentID = NULL) {
e79e9c87
TO
68 if (self::$_cache === NULL) {
69 self::$_cache = array();
def0681b 70 }
6a488035 71
def0681b 72 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
e79e9c87 73 if (!array_key_exists($argString, self::$_cache)) {
def0681b 74 $cache = CRM_Utils_Cache::singleton();
e79e9c87
TO
75 self::$_cache[$argString] = $cache->get($argString);
76 if (!self::$_cache[$argString]) {
def0681b 77 $dao = new CRM_Core_DAO_Cache();
6a488035 78
def0681b
KJ
79 $dao->group_name = $group;
80 $dao->path = $path;
81 $dao->component_id = $componentID;
82
83 $data = NULL;
84 if ($dao->find(TRUE)) {
85 $data = unserialize($dao->data);
86 }
87 $dao->free();
e79e9c87
TO
88 self::$_cache[$argString] = $data;
89 $cache->set($argString, self::$_cache[$argString]);
def0681b 90 }
6a488035 91 }
e79e9c87 92 return self::$_cache[$argString];
6a488035
TO
93 }
94
95 /**
96 * Retrieve all items in a group
97 *
6a0b768e
TO
98 * @param string $group
99 * (required) The group name of the item.
100 * @param int $componentID
101 * The optional component ID (so componenets can share the same name space).
6a488035
TO
102 *
103 * @return object The data if present in cache, else null
104 * @static
6a488035 105 */
00be9182 106 public static function &getItems($group, $componentID = NULL) {
e79e9c87
TO
107 if (self::$_cache === NULL) {
108 self::$_cache = array();
def0681b 109 }
6a488035 110
def0681b 111 $argString = "CRM_CT_CI_{$group}_{$componentID}";
e79e9c87 112 if (!array_key_exists($argString, self::$_cache)) {
def0681b 113 $cache = CRM_Utils_Cache::singleton();
e79e9c87
TO
114 self::$_cache[$argString] = $cache->get($argString);
115 if (!self::$_cache[$argString]) {
def0681b
KJ
116 $dao = new CRM_Core_DAO_Cache();
117
118 $dao->group_name = $group;
119 $dao->component_id = $componentID;
120 $dao->find();
6a488035 121
def0681b
KJ
122 $result = array(); // array($path => $data)
123 while ($dao->fetch()) {
124 $result[$dao->path] = unserialize($dao->data);
125 }
126 $dao->free();
127
e79e9c87
TO
128 self::$_cache[$argString] = $result;
129 $cache->set($argString, self::$_cache[$argString]);
def0681b 130 }
6a488035 131 }
def0681b 132
e79e9c87 133 return self::$_cache[$argString];
6a488035
TO
134 }
135
136 /**
137 * Store an item in the DB cache
138 *
6a0b768e
TO
139 * @param object $data
140 * (required) A reference to the data that will be serialized and stored.
141 * @param string $group
142 * (required) The group name of the item.
143 * @param string $path
144 * (required) The path under which this item is stored.
145 * @param int $componentID
146 * The optional component ID (so componenets can share the same name space).
6a488035
TO
147 *
148 * @return void
149 * @static
6a488035 150 */
00be9182 151 public static function setItem(&$data, $group, $path, $componentID = NULL) {
e79e9c87
TO
152 if (self::$_cache === NULL) {
153 self::$_cache = array();
def0681b
KJ
154 }
155
6a488035
TO
156 $dao = new CRM_Core_DAO_Cache();
157
158 $dao->group_name = $group;
159 $dao->path = $path;
160 $dao->component_id = $componentID;
161
162 // get a lock so that multiple ajax requests on the same page
163 // dont trample on each other
164 // CRM-11234
165 $lockName = "civicrm.cache.{$group}_{$path}._{$componentID}";
166 $lock = new CRM_Core_Lock($lockName);
167 if (!$lock->isAcquired()) {
168 CRM_Core_Error::fatal();
169 }
170
171 $dao->find(TRUE);
172 $dao->data = serialize($data);
173 $dao->created_date = date('YmdHis');
174 $dao->save();
175
176 $lock->release();
177
178 $dao->free();
def0681b 179
80259ba2
TO
180 // cache coherency - refresh or remove dependent caches
181
def0681b
KJ
182 $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
183 $cache = CRM_Utils_Cache::singleton();
184 $data = unserialize($dao->data);
e79e9c87 185 self::$_cache[$argString] = $data;
def0681b 186 $cache->set($argString, $data);
80259ba2
TO
187
188 $argString = "CRM_CT_CI_{$group}_{$componentID}";
e79e9c87 189 unset(self::$_cache[$argString]);
80259ba2 190 $cache->delete($argString);
6a488035
TO
191 }
192
193 /**
194 * Delete all the cache elements that belong to a group OR
195 * delete the entire cache if group is not specified
196 *
6a0b768e
TO
197 * @param string $group
198 * The group name of the entries to be deleted.
199 * @param string $path
200 * Path of the item that needs to be deleted.
554259a7 201 * @param bool|\booleab $clearAll clear all caches
6a488035
TO
202 *
203 * @return void
204 * @static
6a488035 205 */
00be9182 206 public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) {
6a488035
TO
207 $dao = new CRM_Core_DAO_Cache();
208
209 if (!empty($group)) {
210 $dao->group_name = $group;
211 }
212
213 if (!empty($path)) {
214 $dao->path = $path;
215 }
216
217 $dao->delete();
218
219 if ($clearAll) {
220 // also reset ACL Cache
221 CRM_ACL_BAO_Cache::resetCache();
222
223 // also reset memory cache if any
224 CRM_Utils_System::flushCache();
225 }
226 }
227
228 /**
229 * The next two functions are internal functions used to store and retrieve session from
230 * the database cache. This keeps the session to a limited size and allows us to
231 * create separate session scopes for each form in a tab
232 *
233 */
234
235 /**
236 * This function takes entries from the session array and stores it in the cache.
237 * It also deletes the entries from the $_SESSION object (for a smaller session size)
238 *
6a0b768e
TO
239 * @param array $names
240 * Array of session values that should be persisted.
6a488035
TO
241 * This is either a form name + qfKey or just a form name
242 * (in the case of profile)
6a0b768e
TO
243 * @param bool $resetSession
244 * Should session state be reset on completion of DB store?.
6a488035
TO
245 *
246 * @return void
247 * @static
6a488035 248 */
00be9182 249 public static function storeSessionToCache($names, $resetSession = TRUE) {
6a488035
TO
250 foreach ($names as $key => $sessionName) {
251 if (is_array($sessionName)) {
2aa397bc 252 $value = NULL;
6a488035
TO
253 if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
254 $value = $_SESSION[$sessionName[0]][$sessionName[1]];
255 }
256 self::setItem($value, 'CiviCRM Session', "{$sessionName[0]}_{$sessionName[1]}");
2aa397bc
TO
257 if ($resetSession) {
258 $_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
259 unset($_SESSION[$sessionName[0]][$sessionName[1]]);
6a488035 260 }
2aa397bc 261 }
6a488035 262 else {
2aa397bc 263 $value = NULL;
6a488035
TO
264 if (!empty($_SESSION[$sessionName])) {
265 $value = $_SESSION[$sessionName];
266 }
267 self::setItem($value, 'CiviCRM Session', $sessionName);
2aa397bc
TO
268 if ($resetSession) {
269 $_SESSION[$sessionName] = NULL;
270 unset($_SESSION[$sessionName]);
6a488035
TO
271 }
272 }
2aa397bc 273 }
6a488035
TO
274
275 self::cleanup();
276 }
277
278 /* Retrieve the session values from the cache and populate the $_SESSION array
279 *
6a0b768e
TO
280 * @param array $names
281 * Array of session values that should be persisted.
6a488035
TO
282 * This is either a form name + qfKey or just a form name
283 * (in the case of profile)
284 *
285 * @return void
286 * @static
6a488035
TO
287 */
288
b5c2afd0 289 /**
100fef9d 290 * @param string $names
b5c2afd0 291 */
00be9182 292 public static function restoreSessionFromCache($names) {
6a488035
TO
293 foreach ($names as $key => $sessionName) {
294 if (is_array($sessionName)) {
295 $value = self::getItem('CiviCRM Session',
296 "{$sessionName[0]}_{$sessionName[1]}"
297 );
298 if ($value) {
299 $_SESSION[$sessionName[0]][$sessionName[1]] = $value;
300 }
301 }
302 else {
303 $value = self::getItem('CiviCRM Session',
304 $sessionName
305 );
306 if ($value) {
307 $_SESSION[$sessionName] = $value;
308 }
309 }
310 }
311 }
312
313 /**
314 * Do periodic cleanup of the CiviCRM session table. Also delete all session cache entries
315 * which are a couple of days old. This keeps the session cache to a manageable size
316 *
554259a7
EM
317 * @param bool $session
318 * @param bool $table
319 * @param bool $prevNext
320 *
6a488035
TO
321 * @return void
322 * @static
6a488035 323 */
2aa397bc 324 public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE) {
6a488035
TO
325 // clean up the session cache every $cacheCleanUpNumber probabilistically
326 $cleanUpNumber = 757;
327
328 // clean up all sessions older than $cacheTimeIntervalDays days
329 $timeIntervalDays = 2;
330 $timeIntervalMins = 30;
331
332 if (mt_rand(1, 100000) % $cleanUpNumber == 0) {
2aa397bc 333 $session = $table = $prevNext = TRUE;
6a488035
TO
334 }
335
336 if ( ! $session && ! $table && ! $prevNext ) {
337 return;
338 }
339
340 if ( $prevNext ) {
341 // delete all PrevNext caches
342 CRM_Core_BAO_PrevNextCache::cleanupCache();
343 }
344
345 if ( $table ) {
346 // also delete all the action temp tables
347 // that were created the same interval ago
348 $dao = new CRM_Core_DAO();
349 $query = "
350SELECT TABLE_NAME as tableName
351FROM INFORMATION_SCHEMA.TABLES
352WHERE TABLE_SCHEMA = %1
353AND ( TABLE_NAME LIKE 'civicrm_task_action_temp_%'
354 OR TABLE_NAME LIKE 'civicrm_export_temp_%'
355 OR TABLE_NAME LIKE 'civicrm_import_job_%' )
356AND CREATE_TIME < date_sub( NOW( ), INTERVAL $timeIntervalDays day )
357";
358
359 $params = array(1 => array($dao->database(), 'String'));
360 $tableDAO = CRM_Core_DAO::executeQuery($query, $params);
361 $tables = array();
362 while ($tableDAO->fetch()) {
363 $tables[] = $tableDAO->tableName;
364 }
365 if (!empty($tables)) {
366 $table = implode(',', $tables);
367 // drop leftover temporary tables
368 CRM_Core_DAO::executeQuery("DROP TABLE $table");
369 }
370 }
371
372 if ( $session ) {
373 // first delete all sessions which are related to any potential transaction
374 // page
375 $transactionPages = array(
376 'CRM_Contribute_Controller_Contribution',
377 'CRM_Event_Controller_Registration',
378 );
379
380 $params = array(
381 1 => array(date('Y-m-d H:i:s', time() - $timeIntervalMins * 60), 'String'),
382 );
383 foreach ($transactionPages as $trPage) {
384 $params[] = array("%${trPage}%", 'String');
385 $where[] = 'path LIKE %' . sizeof($params);
386 }
387
388 $sql = "
389DELETE FROM civicrm_cache
390WHERE group_name = 'CiviCRM Session'
391AND created_date <= %1
392AND (" . implode(' OR ', $where) . ")";
393 CRM_Core_DAO::executeQuery($sql, $params);
394
395 $sql = "
396DELETE FROM civicrm_cache
397WHERE group_name = 'CiviCRM Session'
398AND created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
399";
400 CRM_Core_DAO::executeQuery($sql);
8c52547a 401 }
6a488035
TO
402 }
403}