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