Merge pull request #888 from cividesk/CRM-12716
[civicrm-core.git] / CRM / Core / Session.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 require_once "PEAR.php";
30 class CRM_Core_Session {
31
32 /**
33 * Cache of all the session names that we manage
34 */
35 static $_managedNames = NULL;
36
37 /**
38 * key is used to allow the application to have multiple top
39 * level scopes rather than a single scope. (avoids naming
40 * conflicts). We also extend this idea further and have local
41 * scopes within a global scope. Allows us to do cool things
42 * like resetting a specific area of the session code while
43 * keeping the rest intact
44 *
45 * @var string
46 */
47 protected $_key = 'CiviCRM';
48 CONST USER_CONTEXT = 'userContext';
49
50 /**
51 * This is just a reference to the real session. Allows us to
52 * debug this class a wee bit easier
53 *
54 * @var object
55 */
56 protected $_session = NULL;
57
58 /**
59 * We only need one instance of this object. So we use the singleton
60 * pattern and cache the instance in this variable
61 *
62 * @var object
63 * @static
64 */
65 static private $_singleton = NULL;
66
67 /**
68 * Constructor
69 *
70 * Since we are now a client / module of drupal, drupal takes care
71 * of initiating the php session handler session_start ().
72 *
73 * When using CiviCRM standalone (i.e. w/o Drupal), we start the session
74 * in index.php and then pass it off to here.
75 *
76 * All crm code should always use the session using
77 * CRM_Core_Session. we prefix stuff to avoid collisions with drupal and also
78 * collisions with other crm modules!!
79 * This constructor is invoked whenever any module requests an instance of
80 * the session and one is not available.
81 *
82 * @return void
83 */
84 function __construct() {
85 $this->_session = null;
86 }
87
88 /**
89 * singleton function used to manage this object
90 *
91 * @return CRM_CoreSession
92 * @static
93 */
94 static function &singleton() {
95 if (self::$_singleton === NULL) {
96 self::$_singleton = new CRM_Core_Session;
97 }
98 return self::$_singleton;
99 }
100
101 /**
102 * Creates an array in the session. All variables now will be stored
103 * under this array
104 *
105 * @param boolean isRead is this a read operation, in this case, the session will not be touched
106 *
107 * @access private
108 *
109 * @return void
110 */
111 function initialize($isRead = FALSE) {
112 // lets initialize the _session variable just before we need it
113 // hopefully any bootstrapping code will actually load the session from the CMS
114 if (!isset($this->_session)) {
115 // CRM-9483
116 if (!isset($_SESSION) && PHP_SAPI !== 'cli') {
117 if ($isRead) {
118 return;
119 }
120 $config =& CRM_Core_Config::singleton();
121 if ($config->userSystem->is_drupal && function_exists('drupal_session_start')) {
122 drupal_session_start();
123 }
124 else {
125 session_start();
126 }
127 }
128 $this->_session =& $_SESSION;
129 }
130
131 if ($isRead) {
132 return;
133 }
134
135 if (!isset($this->_session[$this->_key]) ||
136 !is_array($this->_session[$this->_key])
137 ) {
138 $this->_session[$this->_key] = array();
139 }
140 return;
141 }
142
143 /**
144 * Resets the session store
145 *
146 * @access public
147 *
148 * @return void
149 */
150 function reset($all = 1) {
151 if ($all != 1) {
152 $this->initialize();
153
154 // to make certain we clear it, first initialize it to empty
155 $this->_session[$this->_key] = array();
156 unset($this->_session[$this->_key]);
157 }
158 else {
159 $this->_session = array();
160 }
161
162 return;
163 }
164
165 /**
166 * creates a session local scope
167 *
168 * @param string prefix local scope name
169 * @param boolean isRead is this a read operation, in this case, the session will not be touched
170 *
171 * @access public
172 *
173 * @return void
174 */
175 function createScope($prefix, $isRead = FALSE) {
176 $this->initialize($isRead);
177
178 if ($isRead || empty($prefix)) {
179 return;
180 }
181
182 if (!CRM_Utils_Array::value($prefix, $this->_session[$this->_key])) {
183 $this->_session[$this->_key][$prefix] = array();
184 }
185 }
186
187 /**
188 * resets the session local scope
189 *
190 * @param string local scope name
191 * @access public
192 *
193 * @return void
194 */
195 function resetScope($prefix) {
196 $this->initialize();
197
198 if (empty($prefix)) {
199 return;
200 }
201
202 if (array_key_exists($prefix, $this->_session[$this->_key])) {
203 unset($this->_session[$this->_key][$prefix]);
204 }
205 }
206
207 /**
208 * Store the variable with the value in the session scope
209 *
210 * This function takes a name, value pair and stores this
211 * in the session scope. Not sure what happens if we try
212 * to store complex objects in the session. I suspect it
213 * is supported but we need to verify this
214 *
215 * @access public
216 *
217 * @param string $name name of the variable
218 * @param mixed $value value of the variable
219 * @param string $prefix a string to prefix the keys in the session with
220 *
221 * @return void
222 *
223 */
224 function set($name, $value = NULL, $prefix = NULL) {
225 // create session scope
226 $this->createScope($prefix);
227
228 if (empty($prefix)) {
229 $session = &$this->_session[$this->_key];
230 }
231 else {
232 $session = &$this->_session[$this->_key][$prefix];
233 }
234
235 if (is_array($name)) {
236 foreach ($name as $n => $v) {
237 $session[$n] = $v;
238 }
239 }
240 else {
241 $session[$name] = $value;
242 }
243 }
244
245 /**
246 * Gets the value of the named variable in the session scope
247 *
248 * This function takes a name and retrieves the value of this
249 * variable from the session scope.
250 *
251 * @access public
252 *
253 * @param string name : name of the variable
254 * @param string prefix : adds another level of scope to the session
255 *
256 * @return mixed
257 *
258 */
259 function get($name, $prefix = NULL) {
260 // create session scope
261 $this->createScope($prefix, TRUE);
262
263 if (empty($this->_session) || empty($this->_session[$this->_key])) {
264 return null;
265 }
266
267 if (empty($prefix)) {
268 $session =& $this->_session[$this->_key];
269 }
270 else {
271 if (empty($this->_session[$this->_key][$prefix])) {
272 return null;
273 }
274 $session =& $this->_session[$this->_key][$prefix];
275 }
276
277 return CRM_Utils_Array::value($name, $session);
278 }
279
280 /**
281 * Gets all the variables in the current session scope
282 * and stuffs them in an associate array
283 *
284 * @access public
285 *
286 * @param array vars : associative array to store name/value pairs
287 * @param string Strip prefix from the key before putting it in the return
288 *
289 * @return void
290 *
291 */
292 function getVars(&$vars, $prefix = '') {
293 // create session scope
294 $this->createScope($prefix, TRUE);
295
296 if (empty($prefix)) {
297 $values = &$this->_session[$this->_key];
298 }
299 else {
300 $values = CRM_Core_BAO_Cache::getItem('CiviCRM Session', "CiviCRM_{$prefix}");
301 }
302
303 if ($values) {
304 foreach ($values as $name => $value) {
305 $vars[$name] = $value;
306 }
307 }
308 }
309
310 /**
311 * Set and check a timer. If it's expired, it will be set again.
312 * Good for showing a message to the user every hour or day (so not bugging them on every page)
313 * Returns true-ish values if the timer is not set or expired, and false if the timer is still running
314 * If you want to get more nuanced, you can check the type of the return to see if it's 'not set' or actually expired at a certain time
315 *
316 * @access public
317 *
318 * @param string name : name of the timer
319 * @param int expire : expiry time (in seconds)
320 *
321 * @return mixed
322 *
323 */
324 function timer($name, $expire) {
325 $ts = $this->get($name, 'timer');
326 if (!$ts || $ts < time() - $expire) {
327 $this->set($name, time(), 'timer');
328 return $ts ? $ts : 'not set';
329 }
330 return false;
331 }
332
333 /**
334 * adds a userContext to the stack
335 *
336 * @param string $userContext the url to return to when done
337 * @param boolean $check should we do a dupe checking with the top element
338 *
339 * @return void
340 *
341 * @access public
342 *
343 */
344 function pushUserContext($userContext, $check = TRUE) {
345 if (empty($userContext)) {
346 return;
347 }
348
349 $this->createScope(self::USER_CONTEXT);
350
351 // hack, reset if too big
352 if (count($this->_session[$this->_key][self::USER_CONTEXT]) > 10) {
353 $this->resetScope(self::USER_CONTEXT);
354 $this->createScope(self::USER_CONTEXT);
355 }
356
357 $topUC = array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
358
359 // see if there is a match between the new UC and the top one. the match needs to be
360 // fuzzy since we use the referer at times
361 // if close enough, lets just replace the top with the new one
362 if ($check && $topUC && CRM_Utils_String::match($topUC, $userContext)) {
363 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
364 }
365 else {
366 if ($topUC) {
367 array_push($this->_session[$this->_key][self::USER_CONTEXT], $topUC);
368 }
369 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
370 }
371 }
372
373 /**
374 * replace the userContext of the stack with the passed one
375 *
376 * @param string the url to return to when done
377 *
378 * @return void
379 *
380 * @access public
381 *
382 */
383 function replaceUserContext($userContext) {
384 if (empty($userContext)) {
385 return;
386 }
387
388 $this->createScope(self::USER_CONTEXT);
389
390 array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
391 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
392 }
393
394 /**
395 * pops the top userContext stack
396 *
397 * @param void
398 *
399 * @return the top of the userContext stack (also pops the top element)
400 *
401 */
402 function popUserContext() {
403 $this->createScope(self::USER_CONTEXT);
404
405 return array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
406 }
407
408 /**
409 * reads the top userContext stack
410 *
411 * @param void
412 *
413 * @return the top of the userContext stack
414 *
415 */
416 function readUserContext() {
417 $this->createScope(self::USER_CONTEXT);
418
419 $config = CRM_Core_Config::singleton();
420 $lastElement = count($this->_session[$this->_key][self::USER_CONTEXT]) - 1;
421 return $lastElement >= 0 ? $this->_session[$this->_key][self::USER_CONTEXT][$lastElement] : $config->userFrameworkBaseURL;
422 }
423
424 /**
425 * dumps the session to the log
426 */
427 function debug($all = 1) {
428 $this->initialize();
429 if ($all != 1) {
430 CRM_Core_Error::debug('CRM Session', $this->_session);
431 }
432 else {
433 CRM_Core_Error::debug('CRM Session', $this->_session[$this->_key]);
434 }
435 }
436
437 /**
438 * Fetches status messages
439 *
440 * @param $reset boolean should we reset the status variable?
441 *
442 * @return string the status message if any
443 */
444 function getStatus($reset = FALSE) {
445 $this->initialize();
446
447 $status = NULL;
448 if (array_key_exists('status', $this->_session[$this->_key])) {
449 $status = $this->_session[$this->_key]['status'];
450 }
451 if ($reset) {
452 $this->_session[$this->_key]['status'] = NULL;
453 unset($this->_session[$this->_key]['status']);
454 }
455 return $status;
456 }
457
458 /**
459 * Stores an alert to be displayed to the user via crm-messages
460 *
461 * @param $text string
462 * The status message
463 *
464 * @param $title string
465 * The optional title of this message
466 *
467 * @param $type string
468 * The type of this message (printed as a css class). Possible options:
469 * - 'alert' (default)
470 * - 'info'
471 * - 'success'
472 * - 'error' (this message type by default will remain on the screen
473 * until the user dismisses it)
474 * - 'no-popup' (will display in the document like old-school)
475 *
476 * @param $options array
477 * Additional options. Possible values:
478 * - 'unique' (default: true) Check if this message was already set before adding
479 * - 'expires' how long to display this message before fadeout (in ms)
480 * set to 0 for no expiration
481 * defaults to 10 seconds for most messages, 5 if it has a title but no body,
482 * or 0 for errors or messages containing links
483 *
484 * @static
485 *
486 * @return void
487 */
488 static function setStatus($text, $title = '', $type = 'alert', $options = array()) {
489 // make sure session is initialized, CRM-8120
490 $session = self::singleton();
491 $session->initialize();
492
493 // default options
494 $options += array('unique' => TRUE);
495
496 if (!isset(self::$_singleton->_session[self::$_singleton->_key]['status'])) {
497 self::$_singleton->_session[self::$_singleton->_key]['status'] = array();
498 }
499 if ($text) {
500 if ($options['unique']) {
501 foreach (self::$_singleton->_session[self::$_singleton->_key]['status'] as $msg) {
502 if ($msg['text'] == $text && $msg['title'] == $title) {
503 return;
504 }
505 }
506 }
507 unset($options['unique']);
508 self::$_singleton->_session[self::$_singleton->_key]['status'][] = array(
509 'text' => $text,
510 'title' => $title,
511 'type' => $type,
512 'options' => $options ? json_encode($options) : NULL,
513 );
514 }
515 }
516
517 static function registerAndRetrieveSessionObjects($names) {
518 if (!is_array($names)) {
519 $names = array($names);
520 }
521
522 if (!self::$_managedNames) {
523 self::$_managedNames = $names;
524 }
525 else {
526 self::$_managedNames = array_merge(self::$_managedNames, $names);
527 }
528
529 CRM_Core_BAO_Cache::restoreSessionFromCache($names);
530 }
531
532 static function storeSessionObjects($reset = TRUE) {
533 if (empty(self::$_managedNames)) {
534 return;
535 }
536
537 self::$_managedNames = CRM_Utils_Array::crmArrayUnique(self::$_managedNames);
538
539 CRM_Core_BAO_Cache::storeSessionToCache(self::$_managedNames, $reset);
540
541 self::$_managedNames = NULL;
542 }
543
544 function isEmpty() {
545 // check if session is empty, if so we dont cache
546 // stuff that we can get away with
547 // helps proxies like varnish
548 return empty($_SESSION) ? TRUE : FALSE;
549 }
550 }