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