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