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