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