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