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