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