INFRA-132 - Standardize @inheritDoc tag use
[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
353ffa53 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
6a488035 235 */
00be9182 236 public function set($name, $value = NULL, $prefix = NULL) {
6a488035
TO
237 // create session scope
238 $this->createScope($prefix);
239
240 if (empty($prefix)) {
241 $session = &$this->_session[$this->_key];
242 }
243 else {
244 $session = &$this->_session[$this->_key][$prefix];
245 }
246
247 if (is_array($name)) {
248 foreach ($name as $n => $v) {
249 $session[$n] = $v;
250 }
251 }
252 else {
253 $session[$name] = $value;
254 }
255 }
256
257 /**
258 * Gets the value of the named variable in the session scope
259 *
260 * This function takes a name and retrieves the value of this
261 * variable from the session scope.
262 *
6a488035 263 *
6a0b768e 264 * @param string $name
16b10e64 265 * name of the variable.
6a0b768e 266 * @param string $prefix
16b10e64 267 * adds another level of scope to the session.
6a488035
TO
268 *
269 * @return mixed
6a488035 270 */
00be9182 271 public function get($name, $prefix = NULL) {
6a488035
TO
272 // create session scope
273 $this->createScope($prefix, TRUE);
274
275 if (empty($this->_session) || empty($this->_session[$this->_key])) {
2aa397bc 276 return NULL;
6a488035
TO
277 }
278
279 if (empty($prefix)) {
280 $session =& $this->_session[$this->_key];
281 }
282 else {
283 if (empty($this->_session[$this->_key][$prefix])) {
2aa397bc 284 return NULL;
6a488035
TO
285 }
286 $session =& $this->_session[$this->_key][$prefix];
287 }
288
289 return CRM_Utils_Array::value($name, $session);
290 }
291
292 /**
293 * Gets all the variables in the current session scope
294 * and stuffs them in an associate array
295 *
6a488035 296 *
6a0b768e
TO
297 * @param array $vars
298 * Associative array to store name/value pairs.
299 * @param string $prefix
300 * Will be stripped from the key before putting it in the return.
6a488035
TO
301 *
302 * @return void
6a488035 303 */
00be9182 304 public function getVars(&$vars, $prefix = '') {
6a488035
TO
305 // create session scope
306 $this->createScope($prefix, TRUE);
307
308 if (empty($prefix)) {
309 $values = &$this->_session[$this->_key];
310 }
311 else {
312 $values = CRM_Core_BAO_Cache::getItem('CiviCRM Session', "CiviCRM_{$prefix}");
313 }
314
315 if ($values) {
316 foreach ($values as $name => $value) {
317 $vars[$name] = $value;
318 }
319 }
320 }
321
322 /**
323 * Set and check a timer. If it's expired, it will be set again.
324 * Good for showing a message to the user every hour or day (so not bugging them on every page)
325 * Returns true-ish values if the timer is not set or expired, and false if the timer is still running
326 * 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
327 *
6a488035 328 *
6a0b768e 329 * @param string $name
16b10e64 330 * name of the timer.
6a0b768e 331 * @param int $expire
16b10e64 332 * expiry time (in seconds).
6a488035
TO
333 *
334 * @return mixed
6a488035 335 */
00be9182 336 public function timer($name, $expire) {
6a488035
TO
337 $ts = $this->get($name, 'timer');
338 if (!$ts || $ts < time() - $expire) {
339 $this->set($name, time(), 'timer');
340 return $ts ? $ts : 'not set';
341 }
2aa397bc 342 return FALSE;
6a488035
TO
343 }
344
345 /**
100fef9d 346 * Adds a userContext to the stack
6a488035 347 *
6a0b768e
TO
348 * @param string $userContext
349 * The url to return to when done.
350 * @param bool $check
351 * Should we do a dupe checking with the top element.
6a488035
TO
352 *
353 * @return void
6a488035 354 */
00be9182 355 public function pushUserContext($userContext, $check = TRUE) {
6a488035
TO
356 if (empty($userContext)) {
357 return;
358 }
359
360 $this->createScope(self::USER_CONTEXT);
361
362 // hack, reset if too big
363 if (count($this->_session[$this->_key][self::USER_CONTEXT]) > 10) {
364 $this->resetScope(self::USER_CONTEXT);
365 $this->createScope(self::USER_CONTEXT);
366 }
367
368 $topUC = array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
369
370 // see if there is a match between the new UC and the top one. the match needs to be
371 // fuzzy since we use the referer at times
372 // if close enough, lets just replace the top with the new one
373 if ($check && $topUC && CRM_Utils_String::match($topUC, $userContext)) {
374 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
375 }
376 else {
377 if ($topUC) {
378 array_push($this->_session[$this->_key][self::USER_CONTEXT], $topUC);
379 }
380 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
381 }
382 }
383
384 /**
100fef9d 385 * Replace the userContext of the stack with the passed one
6a488035 386 *
6a0b768e
TO
387 * @param string $userContext
388 * The url to return to when done.
6a488035
TO
389 *
390 * @return void
6a488035 391 */
00be9182 392 public function replaceUserContext($userContext) {
6a488035
TO
393 if (empty($userContext)) {
394 return;
395 }
396
397 $this->createScope(self::USER_CONTEXT);
398
399 array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
400 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
401 }
402
403 /**
100fef9d 404 * Pops the top userContext stack
6a488035 405 *
a6c01b45
CW
406 * @return string
407 * the top of the userContext stack (also pops the top element)
6a488035 408 */
00be9182 409 public function popUserContext() {
6a488035
TO
410 $this->createScope(self::USER_CONTEXT);
411
412 return array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
413 }
414
415 /**
100fef9d 416 * Reads the top userContext stack
6a488035 417 *
a6c01b45
CW
418 * @return string
419 * the top of the userContext stack
6a488035 420 */
00be9182 421 public function readUserContext() {
6a488035
TO
422 $this->createScope(self::USER_CONTEXT);
423
424 $config = CRM_Core_Config::singleton();
425 $lastElement = count($this->_session[$this->_key][self::USER_CONTEXT]) - 1;
426 return $lastElement >= 0 ? $this->_session[$this->_key][self::USER_CONTEXT][$lastElement] : $config->userFrameworkBaseURL;
427 }
428
429 /**
100fef9d 430 * Dumps the session to the log
d4ad6ab3 431 * @param int $all
6a488035 432 */
00be9182 433 public function debug($all = 1) {
6a488035
TO
434 $this->initialize();
435 if ($all != 1) {
436 CRM_Core_Error::debug('CRM Session', $this->_session);
437 }
438 else {
439 CRM_Core_Error::debug('CRM Session', $this->_session[$this->_key]);
440 }
441 }
442
443 /**
444 * Fetches status messages
445 *
6a0b768e
TO
446 * @param bool $reset
447 * Should we reset the status variable?.
6a488035 448 *
a6c01b45
CW
449 * @return string
450 * the status message if any
6a488035 451 */
00be9182 452 public function getStatus($reset = FALSE) {
6a488035
TO
453 $this->initialize();
454
455 $status = NULL;
456 if (array_key_exists('status', $this->_session[$this->_key])) {
457 $status = $this->_session[$this->_key]['status'];
458 }
459 if ($reset) {
460 $this->_session[$this->_key]['status'] = NULL;
461 unset($this->_session[$this->_key]['status']);
462 }
463 return $status;
464 }
465
466 /**
467 * Stores an alert to be displayed to the user via crm-messages
468 *
5a4f6742 469 * @param string $text
6a488035
TO
470 * The status message
471 *
5a4f6742 472 * @param string $title
6a488035
TO
473 * The optional title of this message
474 *
5a4f6742 475 * @param string $type
6a488035
TO
476 * The type of this message (printed as a css class). Possible options:
477 * - 'alert' (default)
478 * - 'info'
479 * - 'success'
480 * - 'error' (this message type by default will remain on the screen
481 * until the user dismisses it)
482 * - 'no-popup' (will display in the document like old-school)
483 *
5a4f6742 484 * @param array $options
6a488035
TO
485 * Additional options. Possible values:
486 * - 'unique' (default: true) Check if this message was already set before adding
487 * - 'expires' how long to display this message before fadeout (in ms)
488 * set to 0 for no expiration
489 * defaults to 10 seconds for most messages, 5 if it has a title but no body,
490 * or 0 for errors or messages containing links
491 *
492 * @static
493 *
494 * @return void
495 */
00be9182 496 public static function setStatus($text, $title = '', $type = 'alert', $options = array()) {
6a488035
TO
497 // make sure session is initialized, CRM-8120
498 $session = self::singleton();
499 $session->initialize();
500
501 // default options
39b795ba 502 $options += array('unique' => TRUE);
6a488035
TO
503
504 if (!isset(self::$_singleton->_session[self::$_singleton->_key]['status'])) {
505 self::$_singleton->_session[self::$_singleton->_key]['status'] = array();
506 }
bc86ffa9 507 if ($text || $title) {
6a488035
TO
508 if ($options['unique']) {
509 foreach (self::$_singleton->_session[self::$_singleton->_key]['status'] as $msg) {
510 if ($msg['text'] == $text && $msg['title'] == $title) {
511 return;
512 }
513 }
514 }
515 unset($options['unique']);
516 self::$_singleton->_session[self::$_singleton->_key]['status'][] = array(
517 'text' => $text,
518 'title' => $title,
519 'type' => $type,
03a7ec8f 520 'options' => $options ? $options : NULL,
6a488035
TO
521 );
522 }
523 }
524
a0ee3941 525 /**
d4ad6ab3 526 * @param string|array $names
a0ee3941 527 */
00be9182 528 public static function registerAndRetrieveSessionObjects($names) {
6a488035
TO
529 if (!is_array($names)) {
530 $names = array($names);
531 }
532
533 if (!self::$_managedNames) {
534 self::$_managedNames = $names;
535 }
536 else {
537 self::$_managedNames = array_merge(self::$_managedNames, $names);
538 }
539
540 CRM_Core_BAO_Cache::restoreSessionFromCache($names);
541 }
542
a0ee3941
EM
543 /**
544 * @param bool $reset
545 */
00be9182 546 public static function storeSessionObjects($reset = TRUE) {
6a488035
TO
547 if (empty(self::$_managedNames)) {
548 return;
549 }
550
551 self::$_managedNames = CRM_Utils_Array::crmArrayUnique(self::$_managedNames);
552
553 CRM_Core_BAO_Cache::storeSessionToCache(self::$_managedNames, $reset);
554
555 self::$_managedNames = NULL;
556 }
557
bb341097
EM
558 /**
559 * Retrieve contact id of the logged in user
72b3a70c
CW
560 * @return integer|NULL
561 * contact ID of logged in user
bb341097 562 */
00be9182 563 public static function getLoggedInContactID() {
bb341097
EM
564 $session = CRM_Core_Session::singleton();
565 if (!is_numeric($session->get('userID'))) {
566 return NULL;
567 }
568 return $session->get('userID');
569 }
570
a0ee3941
EM
571 /**
572 * @return bool
573 */
00be9182 574 public function isEmpty() {
d4ad6ab3 575 // check if session is empty, if so we don't cache
6a488035
TO
576 // stuff that we can get away with
577 // helps proxies like varnish
d4ad6ab3 578 return empty($_SESSION);
6a488035
TO
579 }
580}